Scala學(xué)習(xí)筆記(八) 模式匹配

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í)筆記(一)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容