Scala之旅——模式匹配

模式匹配是检查一个值是否匹配某种模式的机制。一个成功的匹配可以把一个值解析成它的组成部分。它相当于Java中switch的增强版,可以以此来替代一堆的if/else语句。

语法

一个match表达式有个待匹配的值、match关键字以及至少一个case从句。

1
2
3
4
5
6
7
8
9
10
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之间的要给随机整数。xmatch操作符的左操作数,右边是4个样例组成的表达式。最后一个样例_匹配了大于2的所有数字。这些样例也被称为_代值_。

match表达式有一个返回值。

1
2
3
4
5
6
7
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1) // one

这个match表达式返回一个String类型,那是因为所有的case都返回String。所以函数matchTest便返回一个String。

匹配样例类

样例类在模式匹配中特别有用。

1
2
3
4
5
6
7
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是一个抽象父类,另外有3个Notification类型的实现,分别是样例类EmailSMSVoiceRecording。现在我们可以使用这些样例类进行模式匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)。在case Email(email, title, _)emailtitle用于返回值,而body则因_而被忽略。

模式守卫

模式守卫是简单的布尔表达式,使得case子句更具体,要做到这点只需要在模式后添加if <boolean expression>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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在重要人士的的列表中,该模式才会匹配上。

类型匹配

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

1
2
3
4
5
6
7
8
9
10
11
12
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
}

def goIdle根据Device的类型不同会有不同的行为。当模式中需要调用方法时,这会很有用。一般惯例是使用类型的首字母来作为case标识符(如本例子中的pc)。

密封类

特质和类都可以被标记为sealed ,这个时候也意味着它的所有子类必须在相同的文件中进行声明。这样做可以保证所有的子类型都是已知的。

1
2
3
4
5
6
7
8
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 all”子句了。

注意

Scala的模式匹配语句对于由样例类表示的代数类型的匹配时很有用。另外Scala也允许通过使用提取器对象unapply方法,来定义有别于样例类的的模式。

小鹏 wechat
公众号:数据Man
0%