scala之模式匹配

简介

模式可以当做对某个类型,其内部数据在结构上抽象出来的表达式。scala中模式匹配使用match关键字。match可以当做是java风格的switch的广义化。但是有三个区别:

  • scala中的match是一个表达式,可以匹配各种情况;
  • scala的可选分支不会贯穿到下一个case
  • 如果一个模式都没匹配上,会抛出MatchError的异常。一般会添加什么都不做的缺省case

模式的种类

通配模式

通配模式(_)可以匹配任何对象。

1
2
3
4
5
6
7
def judgeGrade(grade:String): Unit = {
grade match {
case _ => println("others")
}
}

judgeGrade("hello") //others

常量模式

常量模式仅匹配自己。任何字面量都可以作为常量模式使用。

1
2
3
4
5
6
7
8
9
10
11
def judgeGrade(grade:String): Unit = {
grade match {
case "A" => println("A")
case "B" => println("B")
case "C" => println("C")
case _ => println("others")
}
}

judgeGrade("A") //A
judgeGrade("B") //B

变量模式

变量模式匹配任何对象。这一点和通配模式相同。不同之处在于变量模式会将对应的变量绑定在匹配的对象上。之后可以用这个变量对对象作进一步的处理。

1
2
3
4
5
6
7
8
9
def matchSomething(something: Int): Unit = {
something match {
case 0 => println("zero")
case something => println("not zero: " + something)
}
}

matchSomething(0) // zero
matchSomething(1) // not zero: 1

上例中something可以匹配任何除0外的Int值。
另外例子:

1
2
3
4
5
6
7
8
9
10
import math.{E, Pi}

def matchPi(x: Double): Unit = {
x match {
case Pi => println("Pi: " + Pi)
case _ => println("OK")
}
}

matchPi(E) // OK

可以看出E并不匹配Piscala采用了一个简单的词法来区分:一个以小写字母打头的简单名称会被当做模式变量处理,所有其他引用都是常量

1
2
3
4
5
6
7
8
9
10
import math.{E, Pi}

def matchpi(x: Double): Unit = {
x match {
case pi => println("Pi: " + pi)
case _ => println("OK")
}
}

matchPi(E) // Pi: 2.718281828459045

构造方法模式

如例所示。假设匹配的是一个样例类(case class),这样模式将首先检查被匹配的对象是否是以这个名称命名的样例类的实例,然后再检查这个对象的改造方法参数是否匹配这些额外给出的模式。如果不是样例类,则需要定义伴生对象并实现unapply方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case class Person(name: String, age: Int)

object ConstructorPattern {
def main(args: Array[String]): Unit = {
val p = Person("stm", 25)

def constructorPattern(p: Person) = {
p match {
case Person(name, age) => println("name: " + name + ", age: " + age)
case _ => "other"
}
}

constructorPattern(p) // name: stm, age: 25
}
}

序列模式

可以和ArrayList等序列类型匹配。在模式中可以给出任意数量的元素。其原理也是通过case class_*可以匹配剩余元素,包括0个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object SequencePattern {
def main(args: Array[String]): Unit = {
val list = List("spark", "hive")
val arr = Array("scala", "java", "python")

def sequencePattern(p: Any) = p match {
case List(_, second, _*) => println(second)
case Array(first, second, _*) => println(first + ", " + second)
case _ => println("other")
}

sequencePattern(list) // hive
sequencePattern(arr) // scala, java
}
}

元组模式

元组模式用于匹配scala中的元组内容。_*不适用于元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object TuplePattern {
def main(args: Array[String]): Unit = {
val tuple1 = ("spark", "hive", "hadoop")
val tuple2 =("java", "python")
def tuplePattern(t:Any) = t match {
case (one, _, _) => println(one)
case (one, two) => println(one + ", " + two)
case _ => println("other")
}

tuplePattern(tuple1) // spark
tuplePattern(tuple2) // java, python
}
}

类型模式

可以用来替代类型测试和类型转换。Map[_, _]匹配任意Map

1
2
3
4
5
6
7
8
9
10
11
12
object TypePattern {
def main(args: Array[String]): Unit = {
def typePattern(t:Any) = t match {
case t :String => println("t.length: " + t.length)
case t :Map[_, _] => println("t.size: " + t.size)
case _ => println("other")
}

typePattern("hello") // t.length: 5
typePattern(Map(1->'a', 2->'b')) // t.size: 2
}
}

类型擦除

javascala中都采用了擦除式的泛型。即在运行中无法判定某个给定的Map对象是用两个Int类型参数创建还是其他类型。但是数组除外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
object TypeErasure {
def main(args: Array[String]): Unit = {
val m1 = Map(1 -> 1, 2 -> 2)
val m2 = Map(1 -> "a", 2-> "b")
def isIntIntMap(x:Any) = x match {
case m:Map[Int, Int] => println(true)
case _ => println(false)
}

isIntIntMap(m1) //true
isIntIntMap(m2) //true

def isStringArray(x:Any) = x match {
case x : Array[String] => println(true)
case _ => println(false)
}

isStringArray(Array("hello")) //true
isStringArray(Array(33)) //false
}
}

变量绑定模式

可以对任何其他模式添加变量。只需要写下变量名、一个@符合模式本身,就得到一个变量绑定模式。如果匹配成功,就将匹配的对象赋值给这个变量。

1
2
3
4
5
6
7
8
9
10
11
12
object VariableBindingPattern {
def main(args: Array[String]): Unit = {
val t = List(List(1,2), List(4,5,6))

def variableBindingPattern(t:Any) = t match {
case List(_, e@List(_, _, _)) => println(e)
case _ => println("other")
}

variableBindingPattern(t) //List(4, 5, 6)
}
}

模式守卫

使用if表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object PatternGuards {
def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3)
val list2 = List(4, 5, 6)

def patternGuards(x: Any) = x match {
case List(first, _*) if first == 1 => println(x)
case _ => println("others")
}

patternGuards(list1) //List(1, 2, 3)
patternGuards(list2) //others
}
}