1. 模式匹配
Scala有一个十分强大的模式匹配机制,可以应用在很多场合:switch语句、类型查询,以及“析构”(获取复杂表达式中的不同部分)。除此之外,Scala还提供了样例类,对模式匹配进行了优化。
1.1 switch
Scala中C风格switch语句的等效代码:
var sign = …
var ch: Char = …
ch match {
case ‘+’ = sign = 1
case ‘-’ = sign = -1
case _ = sign = 0’
}
case _ 模式是捕获所有情况,避免代码抛出MatchError.
与if类似,match也是表达式,而不是语句。前面的代码可以简化为:
sign = ch match {
case ‘+’ => 1
case ‘-’ => -1
case _ => 0’
}
可以在match表达式中使用任何类型,而不仅仅是数字。例如:
val array = Array("red", "green", "yellow")
val color = array(new Random().nextInt(3))
val sign = color match {
case "red" => "红灯"
case "green" => "绿灯"
case _ => "黄灯"
}
println(sign)
1.2 守卫
假如我们向扩展我们的示例以匹配所有数字。可以给模式添加守卫,就像这样:
var sign = 1
val ch = 1
ch match {
case '+' => sign = 1
case '-' => sign = -1
case _ if Character.isDigit(ch) => sign = Character.digit (ch, 6)
case _ => sign = 0
}
守卫可以是任何Boolean条件。
注意:模式总是从上往下进行匹配。如果带守卫的这个模式不能匹配,则捕获所有的模式(case _)会被用来尝试进行匹配。
1.3 模式中的变量
如果case 关键字后面跟着一个变量名,那么匹配的表达式会被赋值给哪个变量。例如:
var sign = 1
val ch = 1
ch match {
case '+' => sign = 1
case '-' => sign = -1
case ch => sign = ch.toInt+100
}
1.4 类型模式
可以对表达式的类型进行匹配,例如:
val obj: Any = "666"
val result = obj match {
case x: Int => x
case s: String => Integer.parseInt(s)
case _: BigInt => Int.MaxValue
case _ => 0
}
在Scala中,使用这种模式匹配比isInstanceOf操作符更方便。当匹配类型时,必须给出一个变量名称。
注意:匹配发生在运行期,Java虚拟机中的泛型类型信息是被擦掉的,因此不能用类型来匹配特定的Map类型。
case m:Map[String, Int] => ... //别这样做!
//可以匹配一个通用的映射:
case m:Map[_, _] => ...
对于数组而言元素的类型信息是完好的。可以匹配到Array[Int].
1.5 匹配数组、列表和元组
要匹配数组的内容,可以在模式中使用Array表达式:
val arr = Array(0,2)
val result = arr match {
case Array(0) => "0000"
case Array(x, y)=> x+" " +y
case Array(0, _*) => "多元素数组"
case _ => "something else"
}
第一个模式匹配只有一个0元素的数组。第二个模式匹配任何带有两个元素的素组,并将这两个元素分别绑定到变量x和y。第三个表达式匹配任何以零开始的素组。
可以以同样的方式匹配列表,使用List表达式。或者,也可以使用::操作符:
val lst = List(0, 2)
val result = lst match {
case 0 :: Nil => "0000"
case x :: y :: Nil => x + " " + y
case 0 :: tail => "多元素列表"
case _ => "something else"
}
对于元组,可以在模式中使用元组表示法:
val pair = (5, 0)
val result = pair match {
case (0, _) => "0000"
case (y, 0) => y + " " + 0
case _ => "something else"
}
1.6 提取器
模式匹配数组、列表和元组,这些功能的背后是提取器(extractor)机制——带有从对象中提取值的unapply或unapplySeq方法的对象。前者用于提取固定数量值的对象,后者提取是一个序列,可长可短。
例如:
arr match {
case Array(0, x) => ...
...
}
Array伴生对象就是一个提取器——它定义了一个unapplySeq方法。该方法被调用时,是以被执行匹配动作的表达式作为参数,而不是模式中看上去像是参数的表达式。Array.unapplySeq(arr) 产出一个序列的值,即数组中的值。第一个值与0进行比较,而第二个值被赋值给x.
正则表达式是另一个适合使用提取器的场景。如果正则表达式有分组,可以用提取器来匹配每个分组。例如:
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
case pattern(num, item) =>...
// 将num设为“99”, item设为“bottles"
}
pattern.unapplySeq(“99 bottles”)产出一系列匹配分组的字符串。这些字符串被分别赋值给了变量num和item.
注意:在这里提取器并非是一个伴生对象,而是一个正则表达式对象。
1.7 变量声明中的模式
变量声明中也可以使用模式匹配,例如:
val (x, y) = (1, 2)
同时把x定义为1,把y定义为2。这对于使用那些返回对偶的函数而言很有用,例如:
val (q, r) = BigInt(10) /% 3
/%方法返回包含商和余数的对偶,而这两个值分别被变量q和r捕获到。同样的语法也可以用于带有变量的模式。例如:
val Array(first, second, _*) = arr
上述代码将数组arr的第一个和第二个元素分别赋值给first和second。
1.8 for表达式中的模式
可以在for推导式中使用带变量的模式。对于每一个遍历到的值,这些变量都会被绑定,例如:
val map = Map(
"a" -> 1,
"b" -> 2,
"c" -> 3,
"d" -> 4
)
for ((k, v) <- map) {
println(k + " -> " + v)
}
1.9 样例类
样例类是一种特殊的类,他们经过优化以被用于模式匹配,例如:
object MatchDemo {
def main(args: Array[String]): Unit = {
// val amt = Dollar(12)
val amt = Currency(4,"hh")
val result = amt match {
case Dollar(v) => "$" + v
case Currency(_, u) => "Oh noes, I got " + u
case Nothing => ""
}
println(result)
}
}
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount
声明样例类时,有如下几件事自动发生:
构造器中的每个参数都成为val——除非它被显式地声明为var(不建议这样做)本质是val修饰的参数只有getter方法,而var修饰的参数有getter和setter方法。
伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象。
提供unapply方法让模式匹配可以工作。
将生成toStirng、equals、hashCode和copy方法。
1.10 copy方法和带名参数
样例类的copy方法创建一个与现有对象相同的新对象。例如:
val amt = Currency(4,"hh")
val price = amt.copy()
println(amt) // Currency(4.0,hh)
println(price) // Currency(4.0,hh)
println(amt==price) // true
println(amt.equals(price)) // true
还可以用带名参数来修改某些属性:
val amt = Currency(4, "hh")
val price = amt.copy(value = 20.1)
val price2 = amt.copy(unit = "CHF")
println(price, "---", price2) // (Currency(20.1,hh),---,Currency(4.0,CHF))