模式匹配是一种检查值是否匹配某种模式的机制。成功的匹配也可以将价值解构成其组成部分(这句翻译有问题)[6]。这是一个比 Java 中的 switch
更强大的版本,也同样被用来代替一系列的 if/else 结构。
语法 (Syntax)
一个 match 表达式有一个待匹配的值, match
关键字和至少一个 case
表达式组成。
import scala.util.Random
val x: Int = Random.nextInt(10)
x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "many"
}
这个常量 x
是从0到10随机出来的。x
是 match
表达式的左操作数。右边则是由四个 case 组成的表达式。最后一个 case _
是一个对不满足上面所有条件的默认 case。
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。
匹配条件类(Matching on 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
。现在我们可以使用这些条件类来进行模式匹配。
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
类型的参数,匹配函数中不同 Notification
子类(i.e. 可能是一个 Email
,SMS
,或者VoiceRecording
)。在 case Email(email, title, _)
表达式中属性 email
和 title
被在下一行返回值的表达式中使用而属性 _
则将被忽略。
模式卫兵(Pattern guards)
模式卫兵是一种简单和布尔类型的表达式,让 case 语句更加具体。只需要在匹配模式后面添加一个 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", "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(email, _, _) if importantPeopleInfo.contains(email)
中,模式只匹配在 importantPeopleInfo
中包含的 email
的 Notification
。
只匹配类型 (Matching on type only)
你可以只匹配类型就像下面一样:
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
的类型不同而有不同的行为。当 case 语句需要调用模式中的方法时,这是很有用的。使用类型的第一个字母作为 case 语句中常量的标识符是一种惯例(上面例子中的 p
和 c
)。
密封类 (Sealed classes)
特征和类可以在关键字之前添加 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 other
语句。
笔记 (Notes)
Scala 中的模式匹配语句在匹配基于代数类型的条件类时是最有用的。Scala 也允许定义独立模式条件类,使用提取器对象(extractor objects)的 unapply
方法。