本节翻译自
综述:模式匹配是一个十分强大的机制,可以应用在很多场合:switch 语句、类型查询,以及“析构”(获取复杂表达式中的不同部分)。样例类针对模式匹配进行了优化。
样例类
样例类就像普通的类一样,但有一些关键的区别,我们将会在下面对它们进行讨论。样例类对建模不可变数据很有帮助。在接下来的步骤中,我们将会看到它们在模式匹配中的重要作用。
定义样例类
一个最小的样例类需要关键字 case class
、一个标识符和一个参数列表(列表可为空):
case class Book(isbn: String)
val frankenstein = Book("978-0486282114")
注意,不需要用关键字 new
来实现 Book
样例类;这是因为样例类有一个默认的 apply
方法,它负责对象的构造。
当您创建带有参数的样例类时,参数是公共的 val
。
case class Message(sender: String, recipient: String, body: String)
val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?")
println(message1.sender) // prints guillaume@quebec.ca
message1.sender = "travis@washington.us" // this line does not compile
你不能重新指定 message1.sender
,因为它是 val
(即不可变的)。样例类允许创建 var
,但并不鼓励这样做。
比较
用结构来比较样例类,而不是引用:
case class Message(sender: String, recipient: String, body: String)
val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val messagesAreTheSame = message2 == message3 // true
尽管 message2
和 message3
引用不同的对象,但每个对象的值是相等的。
复制
你可以通过使用 copy
方法创建一个样例类实例的一个(浅)副本。你还可以选择性地更改构造函数参数。
case class Message(sender: String, recipient: String, body: String)
val message4 = Message("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg")
val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr")
message5.sender // travis@washington.us
message5.recipient // claire@bourgogne.fr
message5.body // "Me zo o komz gant ma amezeg"
message4
的接收者 recipient
,被用作 message5
的发送者 sender
,但是 message4
的 body
被直接复制。
模式匹配
模式匹配是一种根据模式检查值的机制。一个成功的匹配也可以将一个值分解为它的组成部分。它是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 _ => "many"
}
上面的 val x
是一个介于 0 到 10 之间的随机整数。x
变成了 match
操作符的左操作数,右边是一个带有四个样例的表达式。最后一个例子是“捕获所有”任何大于2的数字的情况。样例(case)也被称为替代选择(alternatives)。
匹配表达式有一个值。
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1) // one
这个匹配表达式有一个字符串类型,因为所有的样例都返回字符串。因此,函数 matchTest
返回一个字符串。
匹配样例类
样例类对于模式匹配特别有用。
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
是一个抽象的超类,它有三个具体的 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
的类型(例如,它可以判断它是 Email
、SMS
还是 VoiceRecording
)。在 Email(email, title, _)
中,字段 email
和 title
在返回值中使用,但字段 body
使用 _
而被忽略。
模式守卫
模式守卫只是简单的布尔表达式,用于使情况更具体。只要在模式之后添加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)
中,只有当 email
在重要人物的列表中才会匹配。
仅匹配类型
你可以像下面一样只匹配类型:
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
}
根据 Device
的类型,def goIdle
有不同的行为。当需要调用模式上的方法时,这是很有用的。将类型的第一个字母作为 case 标识符(在本例中是 p
和 c
)是一种约定。
密封类
特征和类可以被标记为 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。
注意事项
Scala的模式匹配语句对于通过case类表示的代数类型的匹配非常有用。Scala还允许独立于case类的模式定义,在提取器对象中使用 unapply
的方法。