1. 程式人生 > 實用技巧 >scala之旅-核心語言特性【模式匹配】(十四)

scala之旅-核心語言特性【模式匹配】(十四)

模式匹配是一個檢查值是否屬於某個模式的機制。成功的匹配可以將一個值分解成多個組成部分。它相當於java中的switch的一個強化版本,並且可以代替if/else的很多場景。

語法

一個匹配表示式有一個值,然後接一個match關鍵字,最後接一個case 短語

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}

上面的 val x 是一個間於0到10的整型隨機數。x 成為操作符 match 左邊操作的物件,右邊則是4個case 表示式。最後一個 case _ 是一個用來捕獲其他所有Int值的。 case 也可以有選擇地進行呼叫。

匹配表示式有一個值。

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
matchTest(3)  // prints other
matchTest(1)  // prints one

這個匹配表示式是String型別,因為它的所有的case 都是返回的String型別。因為函式 matchTest返回一個String 型別。

case類中匹配

case 類在模式匹配中尤為的友好。

abstract class Notification

case class Email(sender: String, title: String, body: String) extends Notification case class SMS(caller: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification

Notification 是一個抽象超類,他一個三個具體的case子類實現類它: Email ,SMS 和 VoiceRecording。 現在我們用這些case型別進行模式匹配:

def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
    case VoiceRecording(name, link) =>
      s"You received a Voice Recording from $name! Click the link to hear it: $link"
  }
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

println(showNotification(someSms))  // prints You got an SMS from 12345! Message: Are you there?

println(showNotification(someVoiceRecording))  // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

方法 showNotification 傳遞一個Notification抽象類作為形參,然後匹配Notification型別(通過識別出它是Email,SMS還是VoiceRecording)。在 case Email(sender,title,_) 中,屬性sender和title被用到了返回值裡面,但是 body屬性被一個 _ 忽略了。

模式監視

模式監視是一個簡單的布林表示式,一般被用來讓case 更加明確。 只需要新增 if <布林表示式> 在模式後面。

def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
  notification match {
    case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
      "You got an email from special someone!"
    case SMS(number, _) if importantPeopleInfo.contains(number) =>
      "You got an SMS from special someone!"
    case other =>
      showNotification(other) // nothing special, delegate to our original showNotification function
  }
}

val importantPeopleInfo = Seq("867-5309", "[email protected]")

val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there?
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone!

println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone!

在 case Email(sender,_,_) ifimportantPeopleInfo.contains(sender) 中,只有當 sender 在重要人列表中才進行匹配。

只做型別匹配

你可以像下面這樣進行匹配:

abstract class Device
case class Phone(model: String) extends Device {
  def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
  def screenSaverOn = "Turning screen saver on..."
}

def goIdle(device: Device) = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}

def goIdle 依賴於 Device的型別會有一個不同行為。這個可以讓你在一個模式匹配中根據型別呼叫對應方法。在匹配前面用標誌符可以很簡便的進行使用(這裡用的p和c做的標誌符)

sealed密封類

特性和類可以用sealed標記,這表明所有的子型別必須要宣告在同一個檔案裡面。這樣可以確保所有的子型別都是可知的。

sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture

def findPlaceToSit(piece: Furniture): String = piece match {
  case a: Couch => "Lie on the couch"
  case b: Chair => "Sit on the chair"
}

這在模式匹配中很有用,因為我們不需要再用一個預設值捕捉其他的型別了。

筆記

Scala的模式匹配語句對於通過case類表示的代數型別最為有用。Scala還允許使用unapply和方法中物件提取器進行定義獨立的case型別進行模式匹配