欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Scala 入门-模式匹配

程序员文章站 2022-03-31 23:02:19
...

专栏原创出处:github-源笔记文件 github-源码 ,欢迎 Star,转载请附上原文出处链接和本声明。

Scala 编程语言专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Scala 编程语言

什么是模式匹配

模式匹配用来检查某一个值是否适用于定义好的一个固定模式,匹配成功可以解构出该值的所有组成元素。
模式匹配是 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", "[email protected]")
  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")
  }
相关标签: Scala 语言