Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)

Case 类(CASE CLASSES)

Case 类就像普通类一样,不过和普通类存在几个关键的区别,接下来我们将介绍一下。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

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第1张图片
因为 message1.senderval 变量(不可变的),所以你不可以给它重新赋值。当然在 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

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第2张图片
即使 message2message3 的引用指向不同的对象, 这两个对象的值还是相等的。

复制

你可以使用 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"

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第3张图片
message4 中的 recipient 被赋值给了 message5sender。但是 message4body是直接复制到了 message5body 中。

模式匹配(PATTERN MATCHING)

模式匹配是一种根据模式检查值的机制。一个成功的匹配也可以将值分解成它的组成部分。它是一个比 Javaswitch 语句更强大的版本,它同样可以被用来替换一系列的 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

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第4张图片
因为匹配表达式中全部的返回 Stringcase 子句都是 String 类型。所以,matchTest 方法返回的是一个字符串(String)。

匹配 case 类

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 类型的实现类,分别是 CaseEmailSMSVoiceRecording
现在我们可以在这些 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

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第5张图片
函数 showNotification 将抽象类型 Notification 作为参数并去匹配 Notification 类型(它可以计算出传入参数是否是 EmailSMSVoiceRecording 类。
case 子句 Email(email,title,_) 中,字段 eamiltitle 用在返回值中,但 body_ 忽略。

模式警卫(Pattern guards)

模式警卫是一个简单的布尔表达式,用于使 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))

Scala之旅- Case 类(CASE CLASSES)和模式匹配(PATTERN MATCHING)_第6张图片
case Email(email, _, _) if importantPeopleInfo.contains(email) 模式中,只有当 emailimportantPeopleInfo 中才会匹配成功。

仅匹配类型

你可以像下面这样匹配类型:

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 类中 pc

密封类(Sealed classes)

Traitsclasses 都可以被标记为 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)的模式定义。

你可能感兴趣的:(scala,Scala之旅(翻译))