scala的模式匹配

scala提供了一個非常強大的模式匹配機制,那什麼是模式匹配呢?模式匹配是檢查某個值(value)是否匹配某一個模式的機制,一個成功的匹配同時會將匹配值解構爲其組成部分。它是Java中的switch語句的升級版,同樣可以用於替代一系列的 if/else 語句。

語法

一個模式匹配包含了一系列備選項,每個都開始於關鍵字 case。每個備選項都包含了一個模式及一到多個表達式。箭頭符號 => 隔開了模式和表達式。

以下是一個簡單的整型值模式匹配實例: 

import scala.util.Random

val x: Int = Random.nextInt(10)

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

 上述代碼中的val x是一個0到10之間的隨機整數,將它放在match運算符的左側對其進行模式匹配,match的右側是包含4條case的表達式,其中最後一個case _表示匹配其餘所有情況,在這裏即是x大於2的情況。

match表達式具有一個結果值:

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

這個match表達式是String類型的,因爲所有的情況(case)均返回String,所以matchTest函數的返回值是String類型。

案例類(case classes)的匹配 

案例類非常適合用於模式匹配。看下面的代碼

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 是一個抽象類,它有三個具體的子類EmailSMSVoiceRecording,我們可以在這些案例類(Case Class)上像這樣使用模式匹配: 

def showNotification(notification: Notification): String = {
  notification match {
    case Email(email, title, _) =>
      s"You got an email from $email 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))  // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

showNotification函數接受一個抽象類Notification對象作爲輸入參數,然後匹配其具體類型。(也就是判斷它是一個EmailSMS,還是VoiceRecording)。在case Email(email, title, _)中,對象的emailtitle屬性在返回值中被使用,而body屬性則被忽略,故使用_代替。

模式守衛(Pattern gaurds) 

爲了讓匹配更加具體,可以使用模式守衛,也就是在模式後面加上if <boolean expression>

def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
  notification match {
    case Email(email, _, _) if importantPeopleInfo.contains(email) =>
      "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("867-5309", "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))
println(showImportantNotification(someVoiceRecording, importantPeopleInfo))
println(showImportantNotification(importantEmail, importantPeopleInfo))
println(showImportantNotification(importantSms, importantPeopleInfo))

case Email(email, _, _) if importantPeopleInfo.contains(email)中,除了要求notificationEmail類型外,還需要email在重要人物列表importantPeopleInfo中,纔會匹配到該模式。

僅匹配類型 

也可以僅匹配類型,如下所示:

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
}

當不同類型對象需要調用不同方法時,僅匹配類型的模式非常有用,如上代碼中goIdle函數對不同類型的Device有着不同的表現。一般使用類型的首字母作爲case的標識符,例如上述代碼中的pc,這是一種慣例。

密封類 

特質(trait)和類(class)可以用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"
}

這對於模式匹配很有用,因爲我們不再需要一個匹配其他任意情況的case

注意,在聲明樣例類時,下面的過程自動發生了: 

(1)構造器的每個參數都成爲val,除非顯式被聲明爲var,但是並不推薦這麼做;

(2)在伴生對象中提供了apply方法,所以可以不使用new關鍵字就可構建對象;

(3)提供unapply方法使模式匹配可以工作;

(4)生成toString、equals、hashCode和copy方法,除非顯示給出這些方法的定義。

如果有寫的不對的地方,歡迎大家指正,如果有什麼疑問,可以加QQ羣:340297350,更多的Flink和spark的乾貨可以加入下面的星球

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