专栏原创出处:github-源笔记文件 (opens new window) ,github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。
Scala 编程语言专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Scala 编程语言 (opens new window)
# 什么是模式匹配
模式匹配用来检查某一个值是否适用于定义好的一个固定模式,匹配成功可以解构出该值的所有组成元素。
模式匹配是 Java 中的 switch 语句的升级版,同样可以用于替代一系列的 if/else 语句。
「案例类」 非常适用于模式匹配,具体内容可以参考《案例类》。
可以利用「提取器对象」中的
unapply
方法来定义非案例类对象的匹配。
# 语法
一个模式匹配语句包括一个待匹配的值,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" // 表示匹配其余所有情况,在这里就是其他可能的整型值。
}
// match 表达式是有返回值的,因为所有的情况均返回 string,所以 matchTest 函数的返回值类型是 string
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "other"
}
# 匹配类型
# 案例类的模式匹配
Scala 会为案例类自动创建伴生对象,而伴生对象中定义了 unapply
方法。
因此案例类很适合用于模式匹配,可以通过模式匹配获取成员属性。
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
def showNotification(notification: Notification): String = {
notification match {
// 对象的 sender 和 title 属性在返回值中被使用,而 body 属性则被忽略,故使用 _ 占位符代替。
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))
// 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
# 模式守卫
为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上 if (boolean expression)
。
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
def show(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"
}
}
def importantShow(notification: Notification, important: Seq[String]): String = {
notification match {
// 此处定义的模式匹配为守卫模式,匹配成功之后还需要进行 if 判断
// 只有是 Email 类型,并且 important 集合中存在 sender 属性才可以匹配成功
case Email(sender, _, _) if important.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if important.contains(number) =>
"You got an SMS from special someone!"
case other =>
// nothing special, delegate to our original show function
show(other)
}
}
val important = Seq("867-5309", "jenny@gmail.com")
val sms = SMS("867-5309", "Are you there?")
val sms2 = SMS("867-5111", "I'm here! Where are you?")
val voice = VoiceRecording("Tom", "voicerecording.org/id/123")
println(importantShow(sms, important))
// You got an SMS from special someone!
println(importantShow(sms2, important))
// You got an SMS from 867-5111! Message: I'm here! Where are you?
println(importantShow(voice, important))
// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
# 仅匹配类型
本模式匹配适用于不同类型的对象需要调用不同的方法的情况。
一般使用类型的首字母作为 case
的标识符。
{
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..."
}
// goIdle 函数对不同类型的 Device 有着不同的表现,采用类型的首字母作为 case 的标识符:p、c
def goIdle(device: Device) = device match {
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
}
}
# 中缀表达式的模式匹配
中缀表达式是一个算数
或逻辑公式
的表示方法,将 操作符
置于 操作数
的中间。
// 例如 :: 是 scala.collection.immutable.List 中定义的一个方法
// def ::[B >: A] (x: B): List[B] = new scala.collection.immutable.::(x, this)
1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)
// 中缀表达式用于模式匹配
List(1,2,3,4,5) match {
// first 为第一个元素,second 为第二个元素,tail 为剩余元素
// 即 List(1,2,3,4,5) = 1 :: 2 :: List(3,4,5)
case first :: second :: tail => println(tail)
}
// List(3, 4, 5)
# 什么是密封类
「特质」 和 「类」 可以用 sealed
标记为密封的,被标记后其所有子类必须与它存在于相同的文件中,从而保证所有子类型都是已知的。
用于模式匹配,当抽象类加了 sealed
之后,scala 在编译的时候会进行检查,如果有漏掉的 case
类型,会警告提示。
// Couch 和 Chair 必须和 Furniture 定义在相同的文件中
sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture
// 由于 Man 加了 sealed 关键字,模式匹配时,scala 会检测是否遗漏匹配的类型,编译时会警告提醒
sealed abstract class Man
case object American extends Man
case object Chinese extends Man
case object Russia extends Man
def from(m: Man) = m match {
case American ⇒ println("American")
case Chinese ⇒ println("Chinese")
}
// Warning:(61, 29) match may not be exhaustive.It would fail on the following input: Russia
// def from(m: Man) = m match {
// 如果确定不会有 Russia 类型的对象传入,可以使用下面的方法去掉警告提示
def from(m: Man) = (m : @unchecked) match {
case American ⇒ println("American")
case Chinese ⇒ println("Chinese")
}