scala学习笔记 - 模式匹配和样例类(一)

25 篇文章 0 订阅
15 篇文章 2 订阅

认识match

    var sign = 0
    val ch: Char = '-'

    ch match {
      case '+' => sign = 1
      case '-' => sign = -1
      case _ => sign = 0
    }

default等效的是捕获所有的case _模式。有这样一个捕获所有的模式是有好处的,否则,如果没有模式能匹配,代码会抛出MatchError。与switch语句不同,Scala模式匹配并不会有“意外掉入下一个分支”的问题,所以不需要break。
与if一样,match也是表达式,而不是语句。上面的代码可以简化为:

    val sign = ch match {
      case '+' => 1
      case '-' => -1
      case _ => 0
    }

|来分隔多个选项:

expression match {
  case "item1" | "item2" | "item3" => ...
  ...
}

可以在match表达式中使用任何类型。

守卫

如果需要匹配多个元素;在scala中,需要给模式添加守卫,如下:

sign = expression match {
  case _ if Character.isDigit(expression) => digit = Character.digit(expression, 10)
  case '+' => 1
  case '-' => -1
  case _ => 0
}

守卫可以是任何Boolean条件。模式总是自上而下进行匹配。

模式中的变量

如果case关键字后面跟着一个变量名,那么匹配的表达式会被赋值给那个变量,也可以在守卫中使用变量。例如:

str(i) match {
  case '+' => 1
  case '-' => -1
  case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10)
}

注意:变量模式可能会与常量表达式相冲突。

import scala.math._
0.5 * c / r match {
  case Pi => ... // 如果0.5 * c / r等于Pi ......
  case x => ... // 否则将x设为0.5 * c / r ......
}

在模式匹配中,变量必须以小写字母开头。如果你有一个小写字母开头的常量,则需要将它写在反引号中。

类型模式

可以对表达式的类型进行匹配。例如:

obj match {
  case i: Int => i
  case s: String => Integer.parseInt(s)
  case _: BigInt => Int.MaxValue
  case _ => 0
}

在scala中,我们更倾向于使用这样的模式匹配,而不是isInstanceOf操作符。
注意模式中的变量名。在第一个模式中,匹配到的值被当作Int绑定到i,而在第二个模式中,值被当作String绑定到s;这里无须用asInstanceOf做类型转换。
注意,当你在匹配类型的时候,必须给出一个变量名。否则,将会拿对象本身来进行匹配。
注意,匹配发生在运行期,JVM中泛型的类型信息是被擦掉的。因此,不能用类型来匹配特定的Map类型。例如:

case map: Map[String, Int] => ... // 别这样做

可以匹配一个通用的映射:

case map: Map[_, _] => ...

不过 ,对于数组而言,元素的类型信息是完好的,可以匹配到Array[Int]

匹配数组、列表和元组

要匹配数组的内容,可以在模式中使用Array表达式,就像这样:

arr match {
  case Array(0) => "0"
  case Array(x, y) => s"$x $y"
  case Array(0, _*) => "0 ... "
  case _ => "something else"
}

第一个模式匹配包含0的数组;第二个模式匹配任何带有两个元素的数组,并将这两个元素分别绑定到变量x和y。第三个表达式匹配任何以0开始的数组。
如果你想将匹配到_*的可变长度参数绑定到变量,可以使用@表示法,就像这样:

case Array(x, rest @ _*) => rest.min

可以使用List的::操作符:

list match {
  case 0 :: Nil => "0"
  case x :: y :: Nil => s"$x $y"
  case 0 :: tail => "0 ..."
  case _ => "something else"
}

元组和上面的一样。
和之前一样,请注意变量是如何绑定到列表或元组的不同部分的。由于这种绑定让你可以很轻松地访问复杂结构的各组成部分,因此这样的操作被称为“析构”。
说明:如果模式有不同的可选分支,你就不能使用除下画线外的其他变量名。例如:

pre match {
  case (_, 0) | (0, _) => ... // 可以;其中一个是0
  case (x, 0) | (0, x) => ... // 错误;不能对可选分支做变量绑定
}

提取器

在上面,你看到了模式是如何匹配数组、列表和元组的;这些功能的背后是提取器(extractor)机制;带有从对象中提取值的unapply和unapplySeq方法的对象。unapply方法用于提取固定数量的对象;而unapplySeq提取的是一个序列,其可长可短。例如:

arr match {
  case Array(x, 0) => x
  case Array(x, rest @ _*) => rest.min
  ...
}

Array伴生对象就是一个提取器,它定义了一个unapplySeq方法,该方法被调用时,以被执行匹配动作的表达式作为参数,而不是模式中看上去像是参数的表达式。Array.unapplySeq(arr)的调用如果成功,结果是一个序列的值,即数组中的值。在第一个case中,如果数组长度为2而第二个元素为0的话,匹配成功; 这时,数组中的第一个元素被赋值给x。
正则表达式是另一个适合使用提取器的场景;如果正则表达式有分组,你可以用提取器来匹配每个分组。例如:

val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
  case pattern(num, item) => println(num, item) // 将"num"赋值为99,item赋值为"bottles"
  // 输出:(99,bottles)
}

pattern.unapplySeq("99 bottles")交出的是一系列匹配分组的字符串,这些字符串被分别赋值给了变量num和item。
注意,在这里提取器并非是一个伴生对象,而是一个正则表达式对象。

变量声明中的模式

可以在变量声明中使用在这样的模式:val (x, y) = (1, 2)。把x赋值为1,y赋值为2。这对于使用那些返回对偶的函数而言很有用,例如:

val (q, r) = BigInt(10) /% 3
println(f"$q, $r") // 输出:3,1

/%方法返回包含商和余数的对偶,而这两个值分别被变量q和r捕获到。同样的语法也可以用于任何带有变量的模式,例如:

val Array(first, second, rest @ _*) = arr

上述代码将数组arr的第一个和第二个元素分别赋值给first和second,并将剩余元素作为一个Seq赋值给rest。
注意:模式中使用的变量必须以小写字母开头。例如,val Array(E, e) = arr,这样的声明,E会被认为是常量,e被认为是变量。
说明:如下表达式,

val p(x1, ..., xn) = e

从定义上讲,跟下面的代码完全一致:

val $result = e match {case p(x1, ..., xn) => (x1, ..., xn)}
val x1 = $result._1
...
val xn = $result._n

其中x1, …, xn都是模式p中的自由变量。即便模式中没有自由变量,这个定义也是成立的。例如:val 2 = x。这是完全合法的Scala代码,只要x在其他地方有定义就好。当你应用这个定时,将得到:

val $result = x match {case 2 => ()}

后面并没有赋值的动作。换句话说,它跟下面的代码是等效的:

if (!2==x) throw new MatchError

for表达式中的模式

可以在for表达式中使用带变量的模式。对每一个遍历到的值,这些变量都会被绑定;这使得我们可以方便地遍历映射,例如:

import scala.collections.JavaConversions.propertiesAsScalaMap
// 将Java的Properties转换成Scala的映射一--只是为了做出一个有意思的示例
for ((k, v) <- System.getProperties()) println(s"$k -> $v")

对映射中的每一个(键,值)对偶,k被绑定到键,而v被绑定到值。for表达式中,失败的匹配将被安静地忽略;举例来说,如下循环将打印出所有对应值为空字符串的键,跳过其他所有的键:

for ((k, "") <- System.getProperties()) println(k)

也可以使用守卫,注意if关键字出现在<-之后:

for ((k, v) <- System.getProperties() if v == "") println(k)

参考:快学scala(第二版)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值