【Scala之旅】样例类与模式匹配

稜镜组合
• 阅读 2606

本节翻译自

综述:模式匹配是一个十分强大的机制,可以应用在很多场合: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

尽管 message2message3 引用不同的对象,但每个对象的值是相等的。

复制

你可以通过使用 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,但是 message4body 被直接复制。

模式匹配

模式匹配是一种根据模式检查值的机制。一个成功的匹配也可以将一个值分解为它的组成部分。它是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 类型,用样例类 EmailSMSVoiceRecording 实现。现在,我们可以对这些样例类进行模式匹配:

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 的类型(例如,它可以判断它是 EmailSMS 还是 VoiceRecording)。在 Email(email, title, _) 中,字段 emailtitle 在返回值中使用,但字段 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 标识符(在本例中是 pc)是一种约定。

密封类

特征和类可以被标记为 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 的方法。

点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
Scala中的match(模式匹配)
/\\模式匹配\/caseclassClass1(param1:String,param2:String)caseclassClass2(param1:String)objectCase{defmain(args:Array\String\){//通过模式匹配进行条件判断valtest1:
Wesley13 Wesley13
3年前
Activiti 工作流入门指南
<divclass"htmledit\_views"id"content\_views"<h1<aname"t0"</a概览</h1<p如我们的介绍部分所述,Activiti目前分为两大类:</p<ul<li<p<ahref"https://activiti.gitbook.io/activiti7deve
Stella981 Stella981
3年前
ASMSupport教程4.8 生成逻辑运算操作
<p在java中有以下逻辑运算符:</p<ul<li&amp;&amp;:条件与</li<li||:条件或</li<li&amp;:布尔型的逻辑与</li<li|:布尔型的逻辑或</li<li^:布尔型的逻辑异或</li<li!:非操作</li</ul<p那么接下来我们将些段例子
Stella981 Stella981
3年前
Python基础教程,Python入门教程(非常详细)
<divclass"htmledit\_views"id"content\_views"<p<ahref"http://c.biancheng.net/python/base/"rel"nofollow"第1章Python编程基础</a</p<p1.<ahref"http://c.biancheng.net/view/
Stella981 Stella981
3年前
Scala
欢迎大家关注:scala工具库(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fjacksu%2Futils4s),里面包含各种库的测试用例和使用说明文档模式匹配使用用模式匹配实现斐波那契deffibonacci(in:Any):
可莉 可莉
3年前
13、scala模式匹配
1、模式匹配的基础语法2、对类型进行模式匹配3、对Array和List的元素进行模式匹配4、caseclass与模式匹配5、Option与模式匹配1、模式匹配的基础语法  Scala提供了matchcase语法,即模式匹配。替代java的switchcase。 
Stella981 Stella981
3年前
IdeaVim
<divid"cnblogs\_post\_body"class"blogpostbodycnblogsmarkdown"<h3id"ideavim简介"IdeaVim简介</h3<pIdeaVim是IntelliJIDEA的一款插件,他提高了我们写代码的速度,对代码的跳转,查找也很友好。</p<ul<li安装位置</
Stella981 Stella981
3年前
ASMSupport教程4.12 生成方法调用操作
<p这一节我们讲如何用ASMSupport生成方法调用的操作,方法调用包括下面四种类型:</p<ol<li调用构造方法<li调用静态方法<li调用非静态方法<li调用当前类的方法<li调用父类方法</li</ol<p首先我们需要看我们想要生成的类:</p<p代码1:</p<h3<divid"scid:9D
Wesley13 Wesley13
3年前
ES6 新增的数组的方法
给定一个数组letlist\//wu:武力zhi:智力{id:1,name:'张飞',wu:97,zhi:10},{id:2,name:'诸葛亮',wu:55,zhi:99},{id:3,name:'赵云',wu:97,zhi:66},{id:4,na
Wesley13 Wesley13
3年前
HTML快捷写法大全
父子用\ \Ulli\3\<ul\    <li\</li\    <li\</li\    <li\</li\</ul\兄弟之间用,也可以省写\pspan\,\ul\<p\</p\<span
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(