模式匹配是检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。
它是Java中的switch
语句的升级版,同样可以用于替代一系列的 if/else 语句。
Scala的模式匹配语句对于使用案例类(case classes)表示的类型非常有用,
同时也可以利用提取器对象(extractor objects)中的unapply
方法来定义非案例类对象的匹配。
语法
一个模式匹配语句包括一个待匹配的值,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" }
案例类(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(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)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
showNotification
函数接受一个抽象类Notification
对象作为输入参数,然后匹配其具体类型。(也就是判断它是一个Email
,SMS
,还是VoiceRecording
)。
在case Email(sender, title, _)
中,对象的sender
和title
属性在返回值中被使用,而body
属性则被忽略,故使用_
代替。
模式守卫(Pattern gaurds)
为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上if <boolean expression>
。
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", "jenny@gmail.com") val someSms = SMS("867-5309", "Are you there?") val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") val importantEmail = Email("jenny@gmail.com", "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(sender, _, _) if importantPeopleInfo.contains(sender)
中,除了要求notification
是Email
类型外,
还需要sender
在重要人物列表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
。