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
是一個抽象類,它有三個具體的子類Email
, SMS
和VoiceRecording
,我們可以在這些案例類(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
對象作爲輸入參數,然後匹配其具體類型。(也就是判斷它是一個Email
,SMS
,還是VoiceRecording
)。在case Email(email, title, _)
中,對象的email
和title
屬性在返回值中被使用,而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)
中,除了要求notification
是Email
類型外,還需要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
的標識符,例如上述代碼中的p
和c
,這是一種慣例。
密封類
特質(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的乾貨可以加入下面的星球