本文最后更新于:2022-05-03T13:45:50+08:00
模式匹配
首先回顾一下Java中的switch-case语法,我们可以使用case来指定多分支的执行逻辑,在每个case语句中需要使用break退出。根据情况会首先进入一个case代码块,一直向下执行直到遇见一个break块或者一直向下执行了所有的代码块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int i = 20 ;switch (i) { case 10 : System.out.println("10" ); break ; case 20 : System.out.println("20" ); break ; case 30 : System.out.println("30" ); break ; default : System.out.println("other number" ); break ; }switch (i) { case 10 : System.out.println("10-1" ); case 20 : System.out.println("20-1" ); case 30 : System.out.println("30-1" ); default : System.out.println("other number-1" ); }
以上代码的执行结果为:
1 2 3 4 20 20-1 30-1 other number-1
Scala中没有switch关键字,但是提供了模式匹配的功能。下面我们介绍在Scala中模式匹配的基本语法。
基本语法
Scala中的模式匹配语法采用match-case
结构。模式匹配采用match关键字声明,每个分支采用case关键字进行声明。匹配会进入对应的case代码块,如果所有的case都没有匹配,则进入case _
分支(类似Java中的default)
1 2 3 4 5 6 value match { case caseVal1 => returnVal1 case caseVal2 => returnVal2 ... case _ => defaultVal }
与Java中不同的是,模式匹配的每个case分支中不用写break语句(Scala中也没有break关键字),在执行的时候,执行完了一个case分支,就直接跳出整个模式匹配的后面(不会像Java中一样还会向后继续执行case分支)
1 2 3 4 5 6 7 val i:Int = 20 i match { case 10 => println("10" ) case 20 => println("20" ) case 30 => println("30" ) case _ => println("other number" ) }
以上代码的运行结果为
match case语句可以匹配任何类型,而不只是字面量。后面会提到这点
=>
后面的代码块,直到下一个case语句之前的代码是作为一个整体执行,可以使用{}
进行包裹,也可以不使用
模式守卫
如果想要表达式匹配某个范围内的数据,则可以在模式匹配中增加条件守卫,即使用条件表达式,例如下面的代码实现了求整数绝对值的功能
1 2 3 4 5 6 7 def abs (num: Int ): Int = { num match { case i if i >= 0 => i case i if i < 0 => -i } } println(abs(-29 ))
模式匹配类型
在Scala中,模式匹配的功能比其他语言中的switch-case
语句要更加强大,它提供了更多的模式匹配类型,包括匹配常量、类型、数组、列表、元组以及对象等。(注意以下的匹配方式都可以混用)
匹配常量
Scala中,模式匹配支持匹配所有的字面量,包括字符串、字符、数字、布尔值等,可以通过传入Any类型的变量来进行匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 def matchConst (x: Any ): String = { x match { case 1 => "Int One" case "hello" => "String hello" case '+' => "Char +" case true => "Boolean true" case a => "other " + a } } println(matchConst(1 )) println(matchConst(2 )) println(matchConst(true ))
注意,这里没有case _
,但是同样存在defalut的情况,就是最后一个case代码块。case _
的情况是在不需要变量的值的时候建议这么做,当然也可以使用一个变量的值来承接,就像这里的case a
,这里的a
承接了不匹配的其他所有的值,应该是Any
类型
匹配类型
需要进行类型判断的时候,可以使用前面说到过的isInstanceOf
和asInstanceOf
,也可以使用模式匹配来匹配类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def matchType (x: Any ): String = { x match { case i: Int => "Int " + i case s: String => "String " + s case list: List [Int ] => "List " + list case array: Array [Int ] => "Array[Int] " + array.mkString(" " ) case somethingElse => "SomethingElse " + somethingElse } } println(matchType(10 )) println(matchType("hello" )) println(matchType(List (1 , 2 , 3 , 4 , 5 ))) println(matchType(List ("hi" , "hello" ))) println(matchType(Array (1 , 2 , 3 , 4 , 5 ))) println(matchType(Array ("hi" , "hello" ))) println(matchType('c'))
以上代码的运行结果如下
1 2 3 4 5 6 7 Int 10 String hello List List(1, 2, 3, 4, 5) List List(hi, hello) Array[Int] 1 2 3 4 5 SomethingElse [Ljava.lang.String;@6bdf28bb SomethingElse c
需要注意的是输出结果的第4行和第6行,第4行中List的泛型和我们定义的不匹配,但是仍旧输出了对应的case语句,而第6行中Array的泛型不匹配,就走了最后一行的default。这里涉及到Scala中泛型擦除的现象,即在类型匹配的时候由于泛型擦除,可能并不能严格匹配泛型的类型参数。但是由于Array是基本数据类型,对应于Java中的原生数组类型,能够匹配泛型类型参数。
匹配数组
Scala中的模式匹配可以对集合进行精确的匹配。可以对数组定义多种匹配方式,例如模糊的元素类型匹配、元素数量匹配或者精确到某个数组元素值匹配,功能非常强大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def matchArray (array: Any ): String = { array match { case Array (0 ) => "0" case Array (1 , 0 ) => "Array(1, 0)" case Array (x: Int , y: Int ) => "Array: " + x + " " + y case Array (0 , _*) => "以0开头的数组" case Array (x, 1 , z) => "中间为1的三元素数组" case Array (x: String , _*) => "第一个元素为字符串的数组" case _ => "Something Else" } } println(matchArray(Array (0 ))) println(matchArray(Array (1 , 0 ))) println(matchArray(Array (1 , 1 , 0 ))) println(matchArray(Array (0 , 1 , 0 ))) println(matchArray(Array (2 , 3 , 4 , 5 , 6 ))) println(matchArray(Array ("String" , 1 , 2 , 3 )))
匹配列表
列表匹配和数组匹配类似,其中可以混入集合类中灵活的运算符
1 2 3 4 5 6 7 8 9 10 11 12 def matchList (list: Any ): String = { list match { case List (0 ) => "0" case List (x, y) => "List(x, y) " + x + "," + y case List (0 , _*) => "List(0, ...)" case _ => "Something Else" } } println(matchList(List (0 ))) println(matchList(List (1 , 0 ))) println(matchList(List (0 , 0 , 0 ))) println(matchList(List ("hello" )))
注意混入集合运算符的用法。下面的代码匹配数组的第一个元素、第二个元素和其余元素
1 2 3 4 5 6 7 8 9 10 def matchList1 (list: Any ): String = { list match { case first :: second::rest =>s"first:${first} second:${second} rest:${rest} " case _ => "Something Else" } } println(matchList1(List (1 , 2 , 3 , 4 , 5 , 6 ))) println(matchList1(List (1 , 2 ))) println(matchList1(List (1 )))
以上代码的输出结果如下,如果rest为空也是能够匹配的
1 2 3 first:1 second:2 rest:List(3, 4, 5, 6) first:1 second:2 rest:List() Something Else
匹配元组
匹配元素类似于其他的集合匹配,可以匹配n元组、元素类型、元素值等。由于元组的大小固定,因此不能使用_*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def matchTuple (tuple: Any ): String = { tuple match { case (a, b) => "(" + a + "," + b + ")" case (0 , _) => "(0,_)" case (x, 1 , z) => "(x, y, z):" + x + " " + 1 + " " + z case _ => "Something Else" } } println(matchTuple((0 , 1 ))) println(matchTuple((0 , 1 , 1 ))) println(matchTuple((0 , 1 ))) println(matchTuple((1 , 1 ))) println(matchTuple(("ture" , '1 ', 1 )))
元组的模式匹配可以用在变量声明以及for推导式中,在后面的应用场景提及到了
匹配对象以及样例类
Scala的模式匹配还能够匹配对象,我们希望完成的是对象的内容匹配。直接在match-case
中匹配对应的引用变量,语法是存在问题的。编译报错信息提示我们需要使用样例类或者实现unapply
方法
要完成对象匹配,我们应该实现伴生对象中的apply
方法和unapply
方法,其中unapply
方法也是一个特殊的方法,用来对对象的属性进行拆解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Student (val name: String , val age: Int )object Student { def apply (name: String , age: Int ): Student = new Student (name, age) def unapply (student: Student ): Option [(String , Int )] = { if (student == null ) None else Some ((student.name, student.age)) } }object Test { def main (args: Array [String ]): Unit = { val student = new Student ("xxx" , 21 ) val result = student match { case Student ("xxx" , 21 ) => "xxx, 21" case _ => "Else" } println(result) } }
在case后面的Student("xxx", 21)
,实际上是调用了伴生对象的apply
方法
当写在case后面,会默认调用unapply
方法(对象提取器),Student对象作为unapply
方法的参数,它将对象的name
和age
属性进行提取,然后与前面的case对象进行匹配
unapply
方法返回Some对象,只有所有属性都一致才算返回成功。属性不一致或者返回None,则匹配失败
以下是一些模板
只提取对象的一个属性:unapply(obj:Obj):Option[T]
提取对象的多个属性:unapply(obj:Obj):Option[T1, T2, T3,...]
提取对象的可变个属性:unapplySeq(obj:Obj):Option[Seq[T]]
也可以使用样例类 的方式来完成,利用case声明一个样例类。样例类是为模式匹配而优化的类,其中自动生成了伴生对象,并且自动提供一些常用的方法,默认提供了如apply
、unapply
、toString
、equals
、hashCode
、copy
等方法,因此无需自己实现unapply
方法。样例类中的每个参数都是val,除非它被显式地声明为var
这样上面的代码可以修改成下面的代码,同样能够完成对应的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 case class Student (val name: String , val age: Int )object Test { def main (args: Array [String ]): Unit = { val student = new Student ("xxx" , 21 ) val result = student match { case Student ("xxx" , 21 ) => "xxx, 21" case _ => "Else" } println(result) } }
一些应用场景
变量声明中的模式匹配
1 2 3 4 5 6 7 8 val (x, y) = (10 , "hello" ) println(s"x: $x , y: $y " )val List (first, second, _*) = List (23 , 15 , 9 , 78 ) println(s"first: $first , second: $second " )val fir :: sec :: rest = List (23 , 15 , 9 , 78 ) println(s"first: $fir , second: $sec , rest: $rest " )
for表达式中的模式匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 val list: List [(String , Int )] = List (("a" , 12 ), ("b" , 35 ), ("c" , 27 ), ("a" , 13 ))for (elem <- list){ println(elem._1 + " " + elem._2) }for ((word, count) <- list ){ println(word + ": " + count) } println("-----------------------" )for ((word, _) <- list) println(word) println("-----------------------" )for (("a" , count) <- list){ println(count) }
偏函数中的模式匹配
偏函数是函数的一种,通过偏函数我们可以方便地对输入参数做更精确的检查,内部就是通过模式匹配来实现的,举例如下
1 2 3 val partialFuncName: PartialFunction [List [Int ], Option [Int ]] = { case x :: y :: _ => Some (y) }
通过一个变量定义方式定义,PartialFunction
的泛型类型中,前者是参数类型,后者是返回值类型。函数体中用一个case
语句来进行模式匹配。上面例子返回输入的List
集合中的第二个元素。
一般一个偏函数只能处理输入的一部分场景,实际中往往需要定义多个偏函数用以组合使用。
偏函数能够实现case条件语句的复用?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 val list: List [(String , Int )] = List (("a" , 12 ), ("b" , 10 ), ("c" , 100 ), ("a" , 5 ))val newList = list.map(tuple => (tuple._1, tuple._2 * 2 )) println(newList)val newList1 = list.map( tuple => { tuple match { case (x, y) => (x, y * 2 ) } } ) println(newList1)val newList2 = list.map { case (x, y) => (x, y * 2 ) } println(newList2)val positiveAbs: PartialFunction [Int , Int ] = { case x if x > 0 => x }val negativeAbs: PartialFunction [Int , Int ] = { case x if x < 0 => -x }val zeroAbs: PartialFunction [Int , Int ] = { case 0 => 0 }def abs (x: Int ): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x) println(abs(-13 )) println(abs(30 )) println(abs(0 ))