天天看點

[Scala]Scala學習筆記十 模式比對

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/SunnyYoona/article/details/77301598

Scala有一個十分強大的模式比對機制.可以應用在很多場合:switch語句,類型檢查等等.此外Scala還提供了樣例類,對模式比對進行了優化.

1. 更好的switch http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#1-switch

如下是Scala中C風格switch語句的等效代碼:

var sign = 2
val ch = '+'
ch match {
  case '+' => sign = 1
  case '-' => sign = -1
  case _ => sign = 0
}
println(sign) // 1
           

與default等效的是捕獲所有模式(

case _模式

).有這樣一個捕獲所有的模式是有好處的.如果沒有模式能比對并且并沒有提供

case _

,代碼會抛出MatchError.

與C語言的switch不同的是,Scala模式比對不會像C語言那樣必須在每個分支的末尾顯示使用break語句防止進行下一分支.

與if類似,match也是表達式,而不是語句.前面代碼可以簡化為:

sign = ch match {
  case '+' => 1
  case '-' => -1
  case _ => 0
}
           

可以在match表達式中使用任何類型,而不僅僅是數字:

color match{
  case Color.RED => ...
  case Color.BLACK => ...
  ...
}
           

2. 守衛 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#2

假如說我們想擴充上面第一個代碼以比對所有數字.在C風格switch語句中可以通過簡單的添加多個case标簽來實作:

switch(i){
  case 0:
    ...
    break;
  case 1:
    ...
    break;
  ...
  case 9:
    ...
    break;  
  default:
    ...
}
           

這如果放在Scala中會更簡單,給模式添加

守衛

(相當于一個判斷條件)即可:

ch match{
  case '+' => sign = 1
  case '-' => sign = -1
  case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10)
  case _ => sign = 0
}
           

守衛可以是任何Boolean條件.

備注

模式總是從上往下進行比對的.如果守衛的這個模式不能比對,則捕獲所有的模式(`case _`)會被用來嘗試進行比對.
           

3. 模式中的變量 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#3

如果case關鍵字後面跟着一個變量名,那麼比對的表達式會被指派給那個變量,進而可以在後面中使用該變量:

val character = '1'
character match {
  case '+' => println("this is +")
  case '-' => println("this is -")
  case ch => println("this is " + ch) // this is 1
}
           
上面代碼中如果給定字元不是'+'或者'-',給定字元則會指派給變量ch
           

也可以在守衛中使用變量:

val character = '5'
character match {
  case '+' => println("this is +")
  case '-' => println("this is -")
  case ch if Character.isDigit(ch) => println("this is digit") // this is digit
}
           

4. 類型模式 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#4

可以根據表達式的類型進行比對:

val str:Any = "Hello World"
str match {
  case s: String => println("this is string " + s) // this is string Hello World
  case x: Int => println("this is integer " + x)
  case ch => println("this is other " + ch)
}
           

在Scala中,我們更傾向于使用這種的模式比對,而不是使用

isInstanceOf

操作符.

注意模式中的變量名.當你在比對類型的時候,必須給出一個變量名(例如上例中的s,x).否則,将會拿對象本身來進行比對:
case _: BigInt => Int.MaxValue // 比對任何類型為BigInt的對象
case BigInt => -1 // 比對類型為Class的BigInt對象
           

5. 比對數組,清單和元組 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#5

比對數組中的内容,可以在模式中使用Array表達式:

def arrayMatch(arr:Array[String]) = arr match {
  case Array("Hello") => println("the array only contain 'Hello'")
  case Array(x,y) => println("the array contain two value " + x + " and " + y)
  case Array(x,_*) => println("the array contain many values " + arr.mkString(","))
  case _ => println("the other array")
}

arrayMatch(Array("Hello")) // the array only contain 'Hello'
arrayMatch(Array("Hello", "World")) // the array contain two value Hello and World
arrayMatch(Array("Hello", "World", "Yoona")) // the array contain many values Hello,World,Yoona
           

同樣也可以使用List表達式(或者使用

::

操作符)比對清單:

def listMatch(list:List[String]) = list match {
  case "Hello" :: Nil => println("the list only contain 'Hello'")
  case x :: y :: Nil => println("the list contain two value " + x + " and " + y)
  case "Hello" :: tail => println("the list contain many values " + list)
  case _ => println("the other list")
}

listMatch(List("Hello")) // the list only contain 'Hello'
listMatch(List("Hello", "World")) // the list contain two value Hello and World
listMatch(List("Hello", "World", "Yoona")) // the list contain many values List(Hello, World, Yoona)
           

同樣也可以使用元組表示法比對元組:

def pairMatch(t:Any) = t match {
  case ("Hello", _) => println("the first value is 'Hello'")
  case (x, "Hello") => println("the first value is " + x + " and the second value is 'Hello'")
  case _ => println("the other tuple")
}

pairMatch(("Hello", "World")) // the first value is 'Hello'
pairMatch(("World", "Hello")) // the first value is World and the second value is 'Hello'
pairMatch(("Hello", "World", "Yoona")) // the other tuple
           

6. 提取器 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#6

前面我們看到模式是如何比對數組,清單和元組的,這些功能背後是

提取器

機制,帶有從對象中提取值的unapply或unapplySeq方法的對象.unapply方法用于提取固定數量的對象,而unapplySeq提取的是一個序列.

arr match {
  case Array(0, x) => ...
  ...
}
           

Array伴生對象就是一個提取器,定義了一個unapplySeq方法.該方法被調用時,以被執行比對動作的表達式作為參數.Array.unapplySeq(arr)産出一個序列的值,即數組中的值.第一個值與零進行比較,二第二個值被指派給x.

正規表達式是另一個适合使用提取器的場景,如果正規表達式中有分組,可以使用提取器來比對每個分組:

val pattern = "([0-9]+ ([a-z]+))".r
val str = "99 bottles"
str match {
  // num = 99 item = bottles
  case pattern(num, item) => ...
  ...
}
           

pattern.unapplySeq("99 bottles")生成一系列比對分組的字元串,這些字元串分别指派給變量num和item.

7. 變量聲明中的模式 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#7

之前我們可以看到模式彙總是可以帶變量的,同樣我們也可以在變量聲明中使用這樣的模式:

// x = 1 y = 2
val (x, y) = (1, 2)
           

這對于使用那些傳回對偶的函數是非常有用的:

val (q, r) = BigInt(10) /% 3
println("q value is " + q + " and r value is " + r)
// q value is 3 and r value is 1
           
/%方法傳回包含商和餘數的對偶,而這兩個值分别被變量q和r捕獲到.
           

8. for表達式中的模式 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E8%84%9A%E6%9C%AC/Scala/%5BScala%5DScala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E5%8D%81%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md#8-for

可以在for推導表達式中使用帶變量的模式.對于每一個周遊到的值,使用模式進行變量綁定:

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, value) <- System.getProperties() ){
  println(key + " = " + value)
}
           

在for表達式中,失敗的比對将被跳過(不會抛出異常),下面循環列印出所有值為空白的鍵,跳過所有其他鍵:

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, "") <- System.getProperties() ){
  println(key + " = ")
}
           

也可以使用守衛(注意if關鍵字出現在<-之後):

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, value) <- System.getProperties() if value == ""){
  println(key + " = " + value)
}           

繼續閱讀