Scala模式匹配(match case)

1. 匹配(match)表達式

Scala的match表達式類似於其他語言中的switch語句,它可以提供給你在多個備選項中做選擇。基本上match表達式可以讓你使用任意的模式(pattern)做選擇。一個簡單的例子如下:

def simpleMatch(fruit: String) {
    fruit match {
      case "apple" => print("apple")
      case "banana" => print("banana")
      case "orange" => print("orange")
      case _ => print("unknown")
    }
}

match表達式檢測參數fruit,如果是字符串"apple"就打印"apple",如果是"banana",就打印"banana",如此類推。默認情況下用下劃線(_)說明,這是常用在Scala裏作爲佔位符來表達未知值的通配符。

與Java的Switch語句比,匹配表達式還有一些重要的差別。其中之一是任意類型的常量,或其他什麼東西,都能當成Scala裏比較用的樣本(Case),而不只是Java的case語句裏面的整數類型和枚舉常量。另一個區別是在每個備選項的最後並沒有break。取而代之的是,break是隱含的,也就是說,不允許從上一個備選項落入到下一個裏面去的情況發生。然而,與Java的switch相比,最顯著的差別或許是match表達式能產生值。在上面的例子裏,match表達式的每個備選項不但可以通過打印輸出值,還可以只生成返回值而不打印:

def simpleMatch(fruit: String) {
    val name = fruit match {
      case "apple" => "apple"
      case "banana" => "banana"
      case "orange" => "orange"
      case _ => "unknown"
    }
    name
 }

2. 樣本類

帶有case修飾符的類被稱爲樣本類(case class),樣本類例子如下:

abstract class Animal                                     
case class Cat(name: String, kind: String) extends Animal 
case class Dog(name: String, kind: String) extends Animal 

上面的類都沒有類結構體,Scala可以去掉圍繞空結構體的花括號,因此class c與class c {} 相同。case修飾符可以讓scala編譯器自動爲你的類添加一些句法上的便捷設定。

首先,它會添加與類名一致的工廠方法。構造對象時不再需使用new關鍵字:

val cat = Cat("tom", "Persian")

第二個便捷設定是樣本類參數列表中的所有參數隱式獲得了val前綴,因此它被當作字段維護:

val cat = Cat("tom", "Persian")             
print(cat.name)                  

第三,是編譯器爲你的類添加了方法toString、hashCode 和 equals的自然實現,它們能夠打印,哈希和比較由類及(遞歸地得到)器所有參數組成的整棵樹。因爲Scala裏的==始終直接轉到equals,這也就意味着樣本類的元素一直是在做結構化的比較。

3. 通配模式

通配模式( _ )匹配任意對象:

val cat = Cat("tom", "Persian")                   
cat match {                                       
  case Cat("tom", "Persian") => print("matched!") 
  case _ => print("not matched!")                 
}
matched!

val cat = Cat("tom", "Persian")                       
cat match {                                           
  case Cat("jetty", "Persian") => print("matched!")   
  case _ => print("not matched!")                     
}
not matched!                                                                                   

通配模式還可以用來忽略對象中你並不關心的部分。比如說,上一個例子實際上並不關心貓的名字和類別,只是檢查是否是貓:

val cat = Cat("tom", "Persian")       
cat match {                           
  case Cat(_, _) => print("matched!") 
  case _ => print("not matched!")     
} 
matched!                                    

match語句中的case 類型一定要是被比較對象的同類型或者子類型,例如下面的語句無法通過編譯:

val animal = Dog("erha", "Husky")                           
animal match {                                              
  case Cat(_, _) => print("the animal is a cat!")           
  case _ => print("the animal is unknown ")                 
} 

Error:(57, 12) constructor cannot be instantiated to expected type;
 found   : Cat
 required: Dog
      case Cat(_, _) => print("the animal is a cat!")

這是因爲變量animal已由Scala的自動類型推導確定爲Dog類型,無法實例爲Cat類型,需要手動指定變量animal爲Animal類型:

val animal: Animal = Dog("erha", "Husky")          
animal match {                                     
  case Cat(_, _) => print("the animal is a cat!")  
  case _ => print("the animal is unknown ")        
}                                                  

the animal is unknown

4. 常量模式

常量模式僅匹配自身。任何字面量都可以用作常量,任何的val或單例對象也可以用作常量。例如,單例對象Nil只匹配空列表模式。

scala>   def describe(x: Any) = x match {
           case 5 => "five"
           case true => "truth"
           case "hello" => "hi!"
           case Nil => "the empty list"
           case _ => "something else"
         }

5. 變量模式

變量模式類似於通配符,可以匹配任意對象。不過與通配符不同的地方在於,Scala把變量綁定在匹配的對象上。因此之後你可以使用這個變量操作對象。

val animal: Animal = Dog("erha", "Husky")            
animal match {                                       
  case Cat(_, _) => print("the animal is a cat!")    
  case other => print("the animal is: " + other)     
}    

the animal is: Dog(erha,Husky)                                                

6. 序列模式

你也可以像匹配樣本類那樣匹配如List或Array這樣的序列類型。下面的例子展示了檢查開始於零的三元素列表的模式:

expr match {                                 
  case List(0, _, _) => print("found it!")   
  case _ =>                                  
}                                            

如果你想匹配一個不指定長度的序列,可以使用_*作爲最後的元素。這種模式能匹配序列中零到任意數量的元素:

expr match {                              
  case List(0, _*) => print("found it!")  
  case _ =>                               
}                                         

7. 模式守衛

模式守衛接在模式之後,開始於if,後接Boolean表達式:

val animal: Animal = Dog("erha", "Husky")         
animal match {                                    
  case Dog(name, kind) if kind.equals("Husky") => 
  case other => print("the animal is: " + other)  
}                                                 

8. 封閉類

一旦你寫好了模式匹配,你就需要確認已經考慮到了所有的情況。例如,定義了一個抽象類和n個子樣本類,你想要在模式匹配語句中考慮所有子類型組合。通常,這在Scala是不可能的,因爲新的樣本子類可以在任意新的文件內被定義。可選方案就是讓樣本類的超類被封閉(sealed)。封閉類除了類定義所在文件之外不能再添加任何子類

sealed abstract class Animal
case class Cat(name: String, kind: String) extends Animal 
case class Dog(name: String, kind: String) extends Animal 

9. Option類型

Scala爲可選值定義了一個名爲Option的標準類型。這種值可以有兩種形式。可以是Some(x)的形式,其中x是實際值,或者可以是None對象,代表缺失的值。

def show(x: Option[String]) = x match {
    case Some(x) => x
    case None => "?"
}

上面代碼背後的原理還是Scala的類型匹配,因爲Some類和None類都是Option的子類,Scala源碼中定義如下:

final case class Some[+A](x: A) extends Option[A] {
  def isEmpty = false
  def get = x
}

case object None extends Option[Nothing] {
  def isEmpty = true
  def get = throw new NoSuchElementException("None.get")
}

10. 模式在變量定義中

在定義val或var的任何時候,都可以使用模式替代簡單的標識符。例如,你可以使用模式拆分元組並把其中的每個值分配給變量:

val myTuple = ("abc", 123)
val (string, number) = myTuple

val dog = Dog("erha", "Husky")
val Dog(name, kind) = dog

11. for表達式裏的模式

 for((country, city) <- capitals) {
      println("the capital of " + country + " is " + city)
 }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章