本文的内容源于对Scala官网关于Pattern matching的教程的翻译。
PATTERN MATCHING
模式匹配是一种根据模式(pattern)检查值(value)的机制。A successful match can also deconstruct a value into its constituent parts. 它是Java中switch
语句的加强版,并且可以用来替代一连串的if/else语句。
语法
一个match表达式包含一个值、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
运算符的左操作数,最右边是一个包含4个case的表达式。最后一个case _
是一个“其余(catch all)”case,在这里表示大于2的任何数字。
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1) // one
这个match表达式中所有case都返回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
是一个抽象的超类,有三个具体实现的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, _)
中,email
和title
字段被返回值引用,而body
(_
)字段被忽略。
Pattern guards
Pattern guard是简单的布尔表达式,使得case更加具体,只要在模式后面加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在重要人物列表中时,该模式才被匹配。
Matching on type only
你还可以仅匹配类型:
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需要调用pattern上的方法时,这是很有用的。使用类名的首字母作为case的标识符是一种惯例(例如这里的p
和c
)。
Sealed classes
特征(trait,类似Java中的interface)和类可以标记为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"
}
这对模式匹配很有用,因为我们不需要“catch all”case。
Notes
Scala的模式匹配语句对于匹配用case类表示的代数类型很有用。Scala还允许用extractor objects的unapply方法定义模式。