简介
模式匹配(pattern-matching),什么是模式(pattern)?,这里的模式用于描述一个结构的组成。
这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。
比如:List("a", _, _*)
也是个pattern,表示第一个元素是 "a"
,后续一个或多个元素的 List
。
简单解读下:
- 首先这个模式首先声明它只会匹配
List
类型的数据结构。 - 其次这个List的第一个元素必须是字符串
"a"
。 - 然后通过
_
占位符来表示,第二位元素必须存在,但是不限制其数据类型、数据内容等等。 - 最后
_*
表示后续的元素也是类型不限,内容不限。*
表示个数都是0或多个的。
在Scala中会经常应用到这个模式匹配的,它能减少if else的判断,能快速的帮你获取到想要的结果情况,并进行下一步的处理。
下面是Scala中的常用例子:
# 匹配一个List,它由三个元素组成,第一个元素为1,第二个元素为2,第三个元素为3
scala> List(1, 2, 3) match { case List(1, 2, 3) => println("ok") }
ok
# 匹配一个List,它至少由一个元素组成,第一个元素为1
scala> List(1, 2, 3) match { case List(1, _*) => println("ok") }
ok
# 匹配一个List,它由三个元素组成,第一个元素为“a",第二个元素任意类型,第三个元素为"c"
scala> List("a", "b", "c") match{ case List("a", _, "c") => println("ok") }
ok
# 当然模式也不仅仅是表示某种结构的,还可以是常量,或类型,如:
scala> val a = 10
a: Int = 10
# 常量模式,如果a与100相等则匹配成功
scala> a match { case 10 => println("ok") }
ok
# 类型模式,如果a是Int类型就匹配成功
scala> a match { case _:Int => println("ok") }
ok
常量匹配
# 例子中case 了一个常量值,然后去匹配s的值。和if语句的效果很像。
scala> val site = "abc"
scala> site match { case "abc" => print("ok") }
scala> val ABC = "abc"
# 注意:匹配模式这里常量必须以大写字母开头。否则 case abc 的话,会将s的赋值到abc身上,而不是模式匹配。
scala> def m(s: String) = s match { case ABC => println("ok") }
scala> m("ab") # 这里会报异常,因为没有case到当前的情况,导致无法处理
scala> m("abc") # 得到ok
变量匹配
变量模式匹配,没有判断变量值,而是单纯的把 site 给赋值到变量 whateverName 上而已。所以这里一般总会匹配成功。
注意:匹配变量这里必须是小写的字母开头,否则会认为是上面的常量匹配。
Future对象的 Future().flatMap { case XX }
就可以理解为变量匹配。
scala> site match { case aaa => println(aaa) }
abc
scala> List(1, 2) match { case List(x, 2) => println(x) }
1
通配符匹配
通配符用下划线表示:_
,可以理解成一个特殊的变量或占位符。通配符通常用于代表所不关心的部分,它不像变量模式可以后续的逻辑中使用这个变量。
单纯的通配符模式通常在模式匹配的最后一行出现,case _ =>
它可以匹配任何对象,用于处理所有其它匹配不成功的情况。这种会处理一些位置的匹配结果情况,从而减少报出异常的情况。
通配符模式也常和其他模式组合使用:
List(1, 2, 3) match {case List(_, _, 3) => println("ok") }
# 上面的 List(_,_,3) 里用了2个通配符表示第一个和第二个元素,这2个元素可以是任意类型
构造器模式
就是去指定一个变量或者class的构造方法的构造规则,然后筛选出符合该匹配规则的变量(或者实例)
# 定义一个case class
scala> case class Person(name: String, age:Int)
defined class Person
# 通过构造器创建一个实例
scala> Person("hanmeimei", 25)
res20: Person = Person(hanmeimei,25)
# 匹配模式的时候可以直接实例的具体内容
scala> res20 match { case Person(_, 25) => print("ok")}
ok
# 也可以在匹配模式的时候,指定赋值的变量,或者声明变量的类型提供更精确的匹配
scala> res20 match { case Person(name, hisAge:Int) => print(name + "-" + hisAge)}
hanmeimei-25
类型匹配
就是判断对象是否是某种类型(在匹配规则处指定变量的类型,而变量可以指定名称,也可以使用通配符):
scala> "hello" match {case _:String => println("ok") }
跟 isInstanceOf
判断类型的效果一样,需要注意的是Scala匹配泛型时要注意,比如:
def m(a: Any) = a match {
case a :List[String] => println("success")
case _ => println("failed")
}
如果使用了泛型,它会被擦拭掉,如同Java的做法,所以上面的 List[String]
里的 String
运行时并不能检测
m(List("A"))
和 m(List(2))
都可以匹配成功。实际上上面的语句编译时就会给出警告,但并不出错。
通常对于泛型直接用通配符替代,上面的写为 case a : List[_] =>
注意:针对带有泛型参数的类型匹配,改为构造器模式可以绕开;
或对泛型参数声明 Manifest/TypeTag 是可以在运行时保持泛型参数类型的信息的。
变量绑定匹配
scala> case class Tree(name:String, tree:Tree)
defined class Tree
scala> Tree("node_1", null)
res26: Tree = Tree(node_1,null)
scala> Tree("root", res26)
res27: Tree = Tree(root,Tree(node_1,null))
scala> res27 match { case Tree(name, Tree(nodeName, _)) => print(s"$name - $nodeName") }
root - node_1
scala> res27 match { case Tree(name, t@Tree(nodeName, _)) => print(s"$name - $nodeName - $t") }
root - node_1 - Tree(node_1,null)
用@符号将匹配到的数据绑定到t变量上,只有匹配成功才会绑定。