模式匹配是一种根据模式检查值的方法。成功的匹配还可以将值分解为其组成部分。它是 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"
}
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 到 9 之间的随机整数。 x
成为 match
运算符的左操作数,右侧是一个有四个 case 的表达式。最后一个 case _
是任何其他可能的 Int
值的“catch all”case。case 也称为备选方案。
匹配表达式具有一个值。
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "other"
}
matchTest(3) // returns other
matchTest(1) // returns one
def matchTest(x: Int): String = x match
case 1 => "one"
case 2 => "two"
case _ => "other"
matchTest(3) // returns other
matchTest(1) // returns one
此匹配表达式具有类型 String,因为所有 case 都返回 String。因此,函数 matchTest
返回一个 String。
匹配 case 类
case 类对于模式匹配特别有用。
sealed trait 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 类实现的具体 Notification 类型 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
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!
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, _, _) if importantPeopleInfo.contains(sender)
中,仅当 sender
在重要人物列表中时,才会匹配模式。
仅匹配类型
您可以像这样匹配类型
sealed trait 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): String = device match {
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
}
sealed trait 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): String = device match
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
def goIdle
根据 Device
的类型具有不同的行为。当 case 需要对模式调用方法时,这很有用。惯例是使用类型的第一个字母作为 case 标识符(在本例中为 p
和 c
)。
密封类型
您可能已经注意到,在上面的示例中,基本类型使用关键字 sealed
进行限定。这提供了额外的安全性,因为当基本类型为 sealed
时,编译器会检查 match
表达式的 cases
是否详尽。
例如,在上面定义的方法 showNotification
中,如果我们忘记一个 case,比如说 VoiceRecording
,编译器会发出警告
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"
}
}
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"
此定义会产生以下警告
match may not be exhaustive.
It would fail on pattern case: VoiceRecording(_, _)
编译器甚至提供了输入示例,这些示例会失败!
另一方面,穷举性检查要求你在与基类型相同的文件中定义基类型的全部子类型(否则,编译器将不知道所有可能的情况)。例如,如果你尝试在定义 sealed trait Notification
的文件之外定义一个新类型的 Notification
,它将产生一个编译错误
case class Telepathy(message: String) extends Notification
^
Cannot extend sealed trait Notification in a different source file
注释
Scala 的模式匹配语句最适用于通过 case 类 表示的代数类型。Scala 还允许使用 提取器对象 中的 unapply
方法独立于 case 类定义模式。
更多资源
- 有关 Scala Book 中匹配表达式的更多详细信息