专栏原创出处: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")
  }
最后修改时间: 2/17/2020, 4:43:04 AM