1. 模式匹配簡(jiǎn)介
模式匹配是 Scala 的重要特性之一,前面兩篇筆記Scala學(xué)習(xí)筆記(六) Scala的偏函數(shù)和偏應(yīng)用函數(shù)、Scala學(xué)習(xí)筆記(七) Sealed Class 和 Enumeration都是為了這一篇而鋪墊準(zhǔn)備的。
在jdk1.7之前,Java的 switch 關(guān)鍵字只可以處理原生類型(int 、short 、byte 、char)和枚舉類型。在jdk1.7以后,switch新增了對(duì)String類型的處理。Scala 雖然沒(méi)有switch關(guān)鍵詞,但是它的模式匹配可以看做是 switch 的加強(qiáng)版,能夠處理更加復(fù)雜的類型和場(chǎng)景。
先來(lái)看一個(gè)簡(jiǎn)單的例子。
scala> def judgeGrade(name:String,grade:String) {
| grade match {
| case "A" => println(name+", you are excellecnt")
| case "B" => println(name+", you are good")
| case "C" => println(name+", you are just so so")
| case _ if name == "Tony" => println(name+", you are a good boy,come on")
| case _ => println("you need to work harder")
| }
| }
judgeGrade: (name: String, grade: String)Unit
scala> judgeGrade("Monica","A")
Monica, you are excellecnt
scala> judgeGrade("Lily","B")
Lily, you are good
scala> judgeGrade("Tom","C")
Tom, you are just so so
scala> judgeGrade("Tony","D")
Tony, you are a good boy,come on
scala> judgeGrade("Jacky","D")
you need to work harder
通過(guò)這個(gè)例子,可以看到模式匹配的語(yǔ)法大致是這樣的。
變量 match {
case 值1 => 代碼
case 值2 => 代碼
...
case 值N if (...) => 代碼
case _ => 代碼
}
注意,case后面的值1到值N,可以是相同類型也可以是不同類型的。
if (...) 是守衛(wèi)條件,后面的例子會(huì)看到。
在最后一行指令中_是一個(gè)通配符,它保證了我們可以處理所有的情況。否則當(dāng)傳進(jìn)一個(gè)不能被匹配的值的時(shí)候,你將獲得一個(gè)運(yùn)行時(shí)錯(cuò)誤。
2. 模式匹配類型
Scala的模式匹配可以支持常量模式、變量模式、序列模式、元組模式、變量綁定模式等等。
2.1常量匹配
case 后面的值是常量。
scala> def matchConstant(x:Any) = x match {
| case 1 => "One"
| case "two" => "Two"
| case "3" => "Three"
| case true => "True"
| case null => "null value"
| case Nil => "empty list"
| case _ => "other value"
| }
matchConstant: (x: Any)String
scala> println(matchConstant(1))
One
scala> println(matchConstant(true))
True
scala> println(matchConstant(null))
null value
scala> println(matchConstant(List())) //匹配到Nil
empty list
scala> println(matchConstant(false))
other value
特別需要注意的是,Nil是一個(gè)空的List,定義為L(zhǎng)ist[Nothing]。iOS開(kāi)發(fā)者會(huì)比較熟悉Nil,但是這里的Nil跟OC中的Nil是兩個(gè)完全不同的概念。
2.2 變量匹配
case 后面的值是變量
scala> def matchVariable(x:Any) = x match {
| case x if(x==1) => x
| case x if(x=="Tony") => x
| case x:String => "other value:" + x
| case _ => "unexpected value:"+x
| }
matchVariable: (x: Any)Any
scala> println(matchVariable(1))
1
scala> println(matchVariable("Tony"))
Tony
scala> println(matchVariable("Scala"))
other value:Scala
scala> println(matchVariable(2))
unexpected value:2
2.3 序列匹配
case 后面的值是數(shù)組、List、Range等集合。
scala> def matchSeq(x:Any) = x match {
| case List("Tony",_,_*) => "Tony is in the list"
| case List(_,second,_*) => "The second is:"+second
| case Array(first,second,_*) => "first:"+first+",second:"+second
| case _ => "Other seq"
| }
matchSeq: (x: Any)String
scala> val list1 = List("Tony","Cafei","Aaron")
list1: List[String] = List(Tony, Cafei, Aaron)
scala> val list2 = "android"::"iOS"::"H5"::Nil
list2: List[String] = List(android, iOS, H5)
scala> val array1 = Array("Hadoop","Spark","ES")
array1: Array[String] = Array(Hadoop, Spark, ES)
scala> val array2 = Array("Scala")
array2: Array[String] = Array(Scala)
scala> println(matchSeq(list1))
Tony is in the list
scala> println(matchSeq(list2))
The second is:iOS
scala> println(matchSeq(array1))
first:Hadoop,second:Spark
scala> println(matchSeq(array2))
Other seq
需要注意的是,
val list2 = "android"::"iOS"::"H5"::Nil
看上去很奇怪,其實(shí)等價(jià)于
val list2 = List("android","iOS","H5")
list分為head和tail兩個(gè)部分,head是list的第一個(gè)元素,tail是list中除了head外的其余元素組成的list。用::連接list時(shí),尾節(jié)點(diǎn)要聲明成Nil。
所以呢,在case后面可以使用::的形式,例如:
scala> def matchSeq2(x:Any) = x match {
| case x::y::Nil => x+" "+y
| case _ => "Something else"
| }
matchSeq2: (x: Any)String
scala> val list3 = List(1,2)
list3: List[Int] = List(1, 2)
scala> println(matchSeq2(list2))
Something else
scala> println(matchSeq2(list3))
1 2
2.4 元組匹配
case 后面的值是元組類型。
scala> def matchTuple(x:Any) = x match {
| case (first,_,_) => first
| case _ => "Something else"
| }
matchTuple: (x: Any)Any
scala> val t = ("Tony","Cafei","Aaron")
t: (String, String, String) = (Tony,Cafei,Aaron)
scala> println(matchTuple(t))
Tony
值得注意的是,在元組模式中不能使用_*來(lái)匹配剩余的元素,_*只適用于序列模式。
2.5 類型匹配
它可以匹配輸入待匹配變量的類型。
scala> def matchType(x:Any) = x match {
| case s:String => "the string length is:"+s.length
| case m:Map[_,_] => "the map size is:"+m.size
| case _:Int | _:Double => "the number is:"+x
| case _ => "unexpected value:"+x
| }
matchType: (x: Any)String
scala> println(matchType("test"))
the string length is:4
scala> println(matchType(1))
the number is:1
scala> println(matchType(1.0d))
the number is:1.0
scala> println(matchType(true))
unexpected value:true
scala> val map = Map("one"->1,"two"->2,"three"->3)
map: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2, three -> 3)
scala> println(matchType(map))
the map size is:3
在這里,case 子句支持"或"邏輯,使用|即可。
如果上述代碼使用Java來(lái)改寫的話,需要不斷地使用instanceof來(lái)做判斷類型。
類型擦除(Type erasure)
上面的類型模式示例中的Map部分,其實(shí)只是匹配了該變量是否為Map類型,并沒(méi)有匹配其中的key和value的類型。如果同時(shí)需要匹配精確的key和value的類型的話,例如下面代碼中匹配key和value都是Int類型的Map,會(huì)提示警告。
scala> def isIntIntMap(x: Any) = x match {
| case m: Map[Int, Int] => true
| case _ => false
| }
<console>:12: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
case m: Map[Int, Int] => true
^
isIntIntMap: (x: Any)Boolean
由于Scala 使用了泛型的類型擦除模式,代碼在運(yùn)行時(shí)會(huì)將類型參數(shù)忽略掉。所以上面的代碼在運(yùn)行時(shí)并不能去判斷當(dāng)前Map對(duì)象的key和value類型是否為Int或其他類型。
scala> isIntIntMap(Map(1->1))
res10: Boolean = true
scala> isIntIntMap(Map("string"->"value"))
res11: Boolean = true
但是Array不會(huì)類型擦除,可以指定Array對(duì)象中元素的類型。
2.6 變量綁定匹配
可以將匹配的對(duì)象綁定到變量上。首先寫一個(gè)變量名,然后寫一個(gè)@符號(hào),最后寫入該匹配的對(duì)象。如果匹配成功,則將變量設(shè)置為匹配的對(duì)象。
scala> case class Person(name: String, age: Int)
defined class Person
scala> val person = Person("Tony",18)
person: Person = Person(Tony,18)
scala> person match {
| case p @Person(_,age) => println(s"${p.name},age is $age")
| case _ => println("Not a person")
| }
Tony,age is 18
3. 模式匹配和Case Class
Case Class在Scala學(xué)習(xí)筆記(四) 類的初步中有提到。
3.1構(gòu)造器模式匹配
case 后面的值是類構(gòu)造器。
scala> case class Person(name:String,age:Int)
defined class Person
scala> val tony = Person("Tony",18)
tony: Person = Person(Tony,18)
scala> val monica = Person("Monica",15)
monica: Person = Person(Monica,15)
scala> val tom = Person("Tom",20)
tom: Person = Person(Tom,20)
scala> def matchConstructor(x:Any) = x match {
| case Person("Tony",18) => println("Hi Tony")
| case Person("Monica",15)=> println("Hi Monica")
| case Person(name,age) => println(s"Who are you,$age year-old person named $name?")
| }
matchConstructor: (x: Any)Unit
scala> matchConstructor(tony)
Hi Tony
scala> matchConstructor(monica)
Hi Monica
scala> matchConstructor(tom)
Who are you,20 year-old person named Tom?
如果在類中聲明了與該類相同的名字的 object 則該object 是該類的“伴生對(duì)象”。伴生對(duì)象有一個(gè)apply()用于構(gòu)造對(duì)象,跟apply()對(duì)偶的是unapply()用于提取和“解構(gòu)”。上面例子的匹配,就是用了Person.unapply(...)。
Person類是case class,創(chuàng)建時(shí)就幫我們實(shí)現(xiàn)了一個(gè)伴生對(duì)象,這個(gè)伴生對(duì)象里定義了apply()和unapply()。
3.2 Sealed Class的模式匹配
使用Sealed Class能保證所有的匹配情況都列舉出來(lái)。
其實(shí),在Scala學(xué)習(xí)筆記(七) Sealed Class 和 Enumeration中,已經(jīng)提到了Sealed Class的模式匹配
4.模式匹配的其他用法
模式匹配并不僅僅局限于case語(yǔ)句。在定義變量時(shí),也可以使用模式匹配。
例如:
scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2
4.1 for循環(huán)中使用
foreach方法
scala> for (i<-List("Java","Scala","Kotlin","Groovy"))
| println(i)
Java
Scala
Kotlin
Groovy
變量綁定,相當(dāng)于給Scala設(shè)置別名index
scala> for(index@"Scala" <- List("Java","Scala","Kotlin","Groovy"))
| println(index)
Scala
條件表達(dá)格式
scala> for((language,"Hadoop") <- Set("Scala" -> "Spark","Java" -> "Hadoop")){
| println(language)
| }
Java
4.2 正則表達(dá)式中使用
scala> val pattern="(S|s)cala".r
pattern: scala.util.matching.Regex = (S|s)cala
scala> val str="Scala is scalable and cool language"
str: String = Scala is scalable and cool language
scala> println(pattern findFirstIn str)
Some(Scala)
scala> println((pattern findAllIn str).mkString(", "))
Scala, scala
scala> println(pattern replaceFirstIn(str, "Java"))
Java is scalable and cool language
Scala 的正則表達(dá)式就是提取器,Scala會(huì)把每個(gè)括號(hào)里的匹配都展開(kāi)到一個(gè)模式變量里。比如"(S|s)cala".r有一個(gè)unapply()方法,它返回Option[String]。另一方面"(S|s)(cala)".r的unapply會(huì)返回Option[String,String]。
scala> val numitemPattern="""([0-9]+) ([a-z]+)""".r
numitemPattern: scala.util.matching.Regex = ([0-9]+) ([a-z]+)
scala> val line="9527 scala"
line: String = 9527 scala
scala> line match{
| case numitemPattern(num,blog)=> println(num+"\t"+blog)
| case _=>println("hahaha...")
| }
9527 scala
4.3 異常處理中使用
Scala 拋出異常的語(yǔ)法和 Java 中的拋出異常語(yǔ)法是一致的。
但是Scala 的try...catch語(yǔ)句和 Java 的有些不一樣,catch語(yǔ)句中通過(guò)case語(yǔ)句來(lái)捕獲對(duì)應(yīng)的異常。
catch {
case e: IllegalArgumentException => println("illegal arg. exception");
case e: IllegalStateException => println("illegal state exception");
case e: IOException => println("IO exception");
}
再結(jié)合一下final語(yǔ)句。
try {
throwsException();
} catch {
case e: IllegalArgumentException => println("illegal arg. exception");
case e: IllegalStateException => println("illegal state exception");
case e: IOException => println("IO exception");
} finally {
println("this code is always executed");
}
4.4 Option類中使用
Scala 語(yǔ)言中包含一個(gè)標(biāo)準(zhǔn)類型 Option 類型,代表可選值。Option 類型的值有兩個(gè)可能的值,一個(gè)為 Some(x) 其中 x 為有效值,另外一個(gè)為 None 對(duì)象,代表空值。
scala> val books=Map("hadoop"->5,"spark"->6,"hbase"->7)
books: scala.collection.immutable.Map[String,Int] = Map(hadoop -> 5, spark -> 6, hbase -> 7)
scala> books.get("hadoop")
res0: Option[Int] = Some(5)
scala> books.get("hive")
res1: Option[Int] = None
scala> books.get("hive").getOrElse("No such book") // 不存在的元素則使用其默認(rèn)的值
res2: Any = No such book
將 Option 類型的值放開(kāi),使用模式匹配:
scala> def matchOption(x:Option[Int]) = x match {
| case Some(s) => s
| case None => "?"
| }
matchOption: (x: Option[Int])Any
scala> matchOption(books.get("hadoop"))
res3: Any = 5
scala> matchOption(books.get("hive"))
res4: Any = ?
scala>
Option[T]實(shí)際上就是一個(gè)容器,可以把它看做是一個(gè)集合,只不過(guò)這個(gè)集合中要么只包含一個(gè)元素(被包裝在Some中返回),要么就不存在元素(返回None)。既然是一個(gè)集合,那么可以對(duì)它使用map、foreach或者filter等方法。
總結(jié)
模式匹配是 Scala 區(qū)別于 Java 的重要特征。我們看到了模式匹配的各種用法,在實(shí)際開(kāi)發(fā)中模式匹配也應(yīng)用于各個(gè)方面。
先前的文章:
Scala學(xué)習(xí)筆記(七) Sealed Class 和 Enumeration
Scala學(xué)習(xí)筆記(六) Scala的偏函數(shù)和偏應(yīng)用函數(shù)
Scala學(xué)習(xí)筆記(五) 抽象類以及類中的一些語(yǔ)法糖
Scala學(xué)習(xí)筆記(四) 類的初步
Scala學(xué)習(xí)筆記(三)
Scala學(xué)習(xí)筆記(二)
Scala學(xué)習(xí)筆記(一)