Scala 學習筆記 模式匹配

1. 模式匹配簡介

模式匹配是 Scala 的重要特性之一,前面兩篇筆記Scala學習筆記(六) Scala的偏函數和偏應用函數Scala學習筆記(七) Sealed Class 和 Enumeration都是爲了這一篇而鋪墊準備的。android

在jdk1.7以前,Java的 switch 關鍵字只能夠處理原生類型(int 、short 、byte 、char)和枚舉類型。在jdk1.7之後,switch新增了對String類型的處理。Scala 雖然沒有switch關鍵詞,可是它的模式匹配能夠看作是 switch 的增強版,可以處理更加複雜的類型和場景。正則表達式

先來看一個簡單的例子。數組

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複製代碼

經過這個例子,能夠看到模式匹配的語法大體是這樣的。app

變量 match { 
  case1 => 代碼
  case2 => 代碼
  ...
  caseN if (...) => 代碼
  case _ => 代碼
}複製代碼

注意,case後面的值1到值N,能夠是相同類型也能夠是不一樣類型的。
if (...) 是守衛條件,後面的例子會看到。
在最後一行指令中_是一個通配符,它保證了咱們能夠處理全部的狀況。不然當傳進一個不能被匹配的值的時候,你將得到一個運行時錯誤。函數

2. 模式匹配類型

Scala的模式匹配能夠支持常量模式、變量模式、序列模式、元組模式、變量綁定模式等等。oop

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是一個空的List,定義爲List[Nothing]。iOS開發者會比較熟悉Nil,可是這裏的Nil跟OC中的Nil是兩個徹底不一樣的概念。this

2.2 變量匹配

case 後面的值是變量spa

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 後面的值是數組、List、Range等集合。scala

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複製代碼

看上去很奇怪,其實等價於

val list2 = List("android","iOS","H5")複製代碼

list分爲head和tail兩個部分,head是list的第一個元素,tail是list中除了head外的其他元素組成的list。用::鏈接list時,尾節點要聲明成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複製代碼

值得注意的是,在元組模式中不能使用_*來匹配剩餘的元素,_*只適用於序列模式。

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來改寫的話,須要不斷地使用instanceof來作判斷類型。

類型擦除(Type erasure)

上面的類型模式示例中的Map部分,其實只是匹配了該變量是否爲Map類型,並無匹配其中的key和value的類型。若是同時須要匹配精確的key和value的類型的話,例以下面代碼中匹配key和value都是Int類型的Map,會提示警告。

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 使用了泛型的類型擦除模式,代碼在運行時會將類型參數忽略掉。因此上面的代碼在運行時並不能去判斷當前Map對象的key和value類型是否爲Int或其餘類型。

scala> isIntIntMap(Map(1->1))
res10: Boolean = true

scala> isIntIntMap(Map("string"->"value"))
res11: Boolean = true複製代碼

可是Array不會類型擦除,能夠指定Array對象中元素的類型。

2.6 變量綁定匹配

能夠將匹配的對象綁定到變量上。首先寫一個變量名,而後寫一個@符號,最後寫入該匹配的對象。若是匹配成功,則將變量設置爲匹配的對象。

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學習筆記(四) 類的初步中有提到。

3.1構造器模式匹配

case 後面的值是類構造器。

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 是該類的「伴生對象」。伴生對象有一個apply()用於構造對象,跟apply()對偶的是unapply()用於提取和「解構」。上面例子的匹配,就是用了Person.unapply(...)。

Person類是case class,建立時就幫咱們實現了一個伴生對象,這個伴生對象裏定義了apply()和unapply()。

3.2 Sealed Class的模式匹配

使用Sealed Class能保證全部的匹配狀況都列舉出來。
其實,在Scala學習筆記(七) Sealed Class 和 Enumeration中,已經提到了Sealed Class的模式匹配

4.模式匹配的其餘用法

模式匹配並不只僅侷限於case語句。在定義變量時,也可使用模式匹配。
例如:

scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2複製代碼

4.1 for循環中使用

foreach方法

scala> for (i<-List("Java","Scala","Kotlin","Groovy"))
     |    println(i)
Java
Scala
Kotlin
Groovy複製代碼

變量綁定,至關於給Scala設置別名index

scala> for(index@"Scala" <- List("Java","Scala","Kotlin","Groovy"))
     |   println(index)
Scala複製代碼

條件表達格式

scala> for((language,"Hadoop") <- Set("Scala" -> "Spark","Java" -> "Hadoop")){
     |   println(language)
     | }
Java複製代碼

4.2 正則表達式中使用

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 的正則表達式就是提取器,Scala會把每一個括號裏的匹配都展開到一個模式變量裏。好比"(S|s)cala".r有一個unapply()方法,它返回Option[String]。另外一方面"(S|s)(cala)".r的unapply會返回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 拋出異常的語法和 Java 中的拋出異常語法是一致的。
可是Scala 的try...catch語句和 Java 的有些不同,catch語句中經過case語句來捕獲對應的異常。

catch {
  case e: IllegalArgumentException => println("illegal arg. exception");
  case e: IllegalStateException    => println("illegal state exception");
  case e: IOException              => println("IO exception");
}複製代碼

再結合一下final語句。

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 語言中包含一個標準類型 Option 類型,表明可選值。Option 類型的值有兩個可能的值,一個爲 Some(x) 其中 x 爲有效值,另一個爲 None 對象,表明空值。

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") // 不存在的元素則使用其默認的值
res2: Any = No such book複製代碼

將 Option 類型的值放開,使用模式匹配:

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]實際上就是一個容器,能夠把它看作是一個集合,只不過這個集合中要麼只包含一個元素(被包裝在Some中返回),要麼就不存在元素(返回None)。既然是一個集合,那麼能夠對它使用map、foreach或者filter等方法。

總結

模式匹配是 Scala 區別於 Java 的重要特徵。咱們看到了模式匹配的各類用法,在實際開發中模式匹配也應用於各個方面。

先前的文章:
Scala學習筆記(七) Sealed Class 和 Enumeration
Scala學習筆記(六) Scala的偏函數和偏應用函數
Scala學習筆記(五) 抽象類以及類中的一些語法糖
Scala學習筆記(四) 類的初步
Scala學習筆記(三)
Scala學習筆記(二)
Scala學習筆記(一)

相關文章
相關標籤/搜索