Case 类就像普通类一样,不过和普通类存在几个关键的区别,接下来我们将介绍一下。Case 类有助于对不可变的数据建模。后面也会介绍 Case 类在模式匹配中的应用。
一个最简单的 case
类需要关键字 case class
,名称标识符和参数列表(参数列可以为空):
case class Book(isbn: String)
val frankenstein = Book("978-0486282114")
注意是如何不用 new
关键字来实例化 case
类型的 book
类的。原因是因为 case
类有一个默认的 apply
方法用于构造对象。
当你创建带参数的 case
类时,这些参数的访问权限默认是公开(public
)的,变量是 val
类型的。
case class Message(sender: String, recipient: String, body: String)
val message1 = Message("[email protected]", "[email protected]", "Ça va ?")
println(message1.sender) // prints [email protected]
message1.sender = "[email protected]" // this line does not compile
因为 message1.sender
是 val
变量(不可变的),所以你不可以给它重新赋值。当然在 case
类中是可以使用 var
变量的,不过这种做法是让人难以接受的。
case
类是根据内容结构进行比较而不是引用。
case class Message(sender: String, recipient: String, body: String)
val message2 = Message("[email protected]", "[email protected]", "Com va?")
val message3 = Message("[email protected]", "[email protected]", "Com va?")
val messagesAreTheSame = message2 == message3 // true
即使 message2
和 message3
的引用指向不同的对象, 这两个对象的值还是相等的。
你可以使用 copy
方法简单的创建一个 case
类实例的(浅层)副本。你也可以选择性的修改构造函数中的参数值。
case class Message(sender: String, recipient: String, body: String)
val message4 = Message("[email protected]", "[email protected]", "Me zo o komz gant ma amezeg")
val message5 = message4.copy(sender = message4.recipient, recipient = "[email protected]")
message5.sender // [email protected]
message5.recipient // [email protected]
message5.body // "Me zo o komz gant ma amezeg"
message4
中的 recipient
被赋值给了 message5
的 sender
。但是 message4
的 body
是直接复制到了 message5
的 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 之间的随机整数。match
操作符左边的 x
是左操作数,右边包含一个拥有四个 case
子句的表达式。表达式最后的 case 子句 _
是用于捕捉比任何 case
数字 2 更大的情况 。 Cases 也称作备选方案(alternatives)。
含有一个参数的匹配表达式
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1) // one
因为匹配表达式中全部的返回 String
的 case
子句都是 String
类型。所以,matchTest
方法返回的是一个字符串(String
)。
Case 类对模式匹配特别有用。
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
类型的实现类,分别是 Case 类 Email
、SMS
和 VoiceRecording
。
现在我们可以在这些 case
类上进行模式匹配:
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
类。
在 case
子句 Email(email,title,_)
中,字段 eamil
和 title
用在返回值中,但 body
被 _
忽略。
模式警卫是一个简单的布尔表达式,用于使 case
子句更具体。只要在模式后添加 if
表达式。
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)
模式中,只有当 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
子句需要调用模式方法时,这会很有用。使用类型首字母作为名称标识符是惯例。(例如 case
类中 p
和 c
)
Traits 和 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"
}
这种定义对于模式来说非常有用,因为我们不需要捕获全部的情况。即不必考虑除声明类之外的其它类。
Scala 的模式匹配语句对于通过 case类表示的代数表达式的匹配是最有用的。通过使用提取器对象(extractor objects)中的 unapply
方法,Scala 也允许独立于 case 类(case classes)的模式定义。