【Scala】模式匹配和样本类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【Scala】模式匹配和样本类相关的知识,希望对你有一定的参考价值。

参考技术A

要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式是数据结构上的,这个模式用于描述一个结构的组成。

我们很容易联想到“正则表达”里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。比如正则表达式里 "^A.*" 这个pattern 表示以A开头、后续一个或多个字符组成的字符串;List("A", _, _*) 也是个pattern,表示第一个元素是”A”,后续一个或多个元素的List。

match表达式可以看做是Java风格switch的泛化。当每个模式都是常量并且最后一个模式可以是通配的时候,Java风格的switch可以被自然地表达为match表达式。但有三点不同需要牢记:

1、 通配模式 (_)匹配任意对象,它被用作默认的“全匹配(catch-all)”的备选项
2、 常量模型 仅匹配自身,任何字面量都可以用作常量
3、 变量模式 类似于通配模式,它可以匹配任意对象。与通配符(_)不同的是,Scala把变量绑定在匹配的对象上。

4、 构造器模式 提供了深度匹配(deep match),如果备选项是样本类,那么构造器模式首先检查对象是否为该备选项的样本类实例,然后检查对象的构造器参数是否符合额外提供的模式。
构造器模式不只检查顶层对象是否一致,还会检查对象的内容是否匹配内层的模式。由于额外的模式自身可以形成构造器模式,因此可以使用它们检查到对象内部的任意深度。

5、 序列模式 可以像匹配样本类那样匹配如List或者Array这样的序列类型。

6、 元组模式 匹配元祖
7、 类型模式 可以当做类型测试和类型转换的简易替代。

带有 case 修饰符的类称为样本类(case class)。这种修饰符可以让Scala编译器自动为你的类添加一些句法上的便捷性。

这些便捷性的代价就是必须写 case 修饰符并且样本类和对象都因为附加的方法及对于每个构造器参数添加了隐含的字段而变得大了一点。
样本类是一种特殊的类,它经过优化以被用于模式匹配。

封闭类除了类定义所在的文件之外不能再添加任何新的子类。其用于模式匹配的另外一个作用是,当你用样本类来做模式匹配是,你可能想让编译器帮你确保你已经列出了所有可能的选择。为了达到这个目的,你需要将样本类的通用超类声明为 sealed 。如果你使用继承自封闭类的样本类做匹配,编译器将通过通知警告信息标识出缺失的模式组合。
举个例子:

如果想要让编译器不进行警告提示的话,需要给匹配的选择器表达式添加 @unchecked 注解。
像是这样 def describe(a: Amount): String = (a: @unchecked) match 。
如果某个类是封闭的,那么在编译器所有子类就是可知的,因而编译器可以检查模式语句的完整性。让所有(同一组)样本类都扩展某个封闭类或特质是个好的做法。

标准类库中的Option类型用样本类来表示那种可能存在、也可能不存在的值。可以是Some(value)的形式,其中value是实际的值;也可以是None对象,代表缺失的值。
Scala集合类的某些标准操作会产生可选值。例如Scala的Map的get方法会发现了指定键的情况下产生Some(value),在没有找到指定键的时候产生None。
举例如下:

样本类None的形式比空字符串的意图更加清晰,比使用null来表示缺少某值的做法更加安全。
Option支持泛型。举例来说,Some(Paris)的类型为Option[String]。

分离可选值最通用的办法是通过模式匹配的方式,举例如下:

Scala鼓励对Option的使用以说明值是可选的。这种处理可选值的方式有若干超越Java的优点。

话说模式匹配(1): 什么是模式?

Scala笔记--模式匹配

模式匹配(pattern matching)在Scala被广泛使用的特性中排在第二位,仅次于函数值和闭包。Scala对于模式匹配的出色支持意味着,在并发编程中在处理Actor接收到的消息时,将会大量地使用它。

Scala的模式匹配非常灵活,可以匹配字面量和常量,以及使用通配符匹配任意的值、元组和列表,甚至还可以根据类型以及判定守卫来进行匹配。

1 基础用法

scala的模式匹配类似于Java的Switch语句,但是比Switch语句强大得多。

1.1 入门案例

val i: Int = 5
val s: String = i match {
  case 1 => "one"
  case 2 => "two"
  case 3 => "three"
  case 4 => "four"
  case _ => "other"
}
println(s)

1.2 匹配函数参数

我们可以通过匹配函数的参数来进一步简化二元加减乘除等操作。

def matchDualOp(a: Int, b: Int, op: Char): Int = op match {
  case '+' => a + b
  case '-' => a - b
  case '*' => a * b
  case '/' => a / b
  case _ => -1
}
println(matchDualOp(2, 1, '+'))
println(matchDualOp(2, 1, '-'))
println(matchDualOp(2, 1, '*'))
println(matchDualOp(2, 1, '/'))
//    println(matchDualOp(2,0,'/'))         // 报错 / by zero

1.3 模式守卫

1.2的例子其实有些地方需要改进,因为除法操作中,b不能为0,所以这里可以使用模式守卫进行改进。

def matchDualOp_(a: Int, b: Int, op: Char): Int = op match {
  case '+' => a + b
  case '-' => a - b
  case '*' => a * b
  case '/' if b != 0 => a / b
  case _ => {
    print("无法匹配")
    -1
  }
}
println(matchDualOp_(2, 1, '+'))
println(matchDualOp_(2, 1, '-'))
println(matchDualOp_(2, 1, '*'))
println(matchDualOp_(2, 1, '/'))
println(matchDualOp_(2, 0, '/'))

2 集合匹配

2.1 匹配常量

def describeConst(x: Any): String = x match {
  case 1 => "Int one"
  case "hello" => "String hello"
  case true => "Boolean true"
  case '+' => "Char +"
  case _ => ""
}
println(describeConst("hello"))
println(describeConst('+'))
println(describeConst(0.3))

2.2 匹配数据类型

def describeType(x: Any): String = x match {
  case i: Int => "Int " + i
  case s: String => "String " + s
  case list: List[String] => "List " + list
  case array: Array[Int] => "Array[Int] " + array.mkString(",")
  case a => "Something else: " + a
}
println(describeType(35))
println(describeType("hello"))
println(describeType(List("hi", "hello")))
println(describeType(List(2, 23)))
println(describeType(Array("hi", "hello")))
println(describeType(Array(2, 23)))

2.3 匹配数组

for (arr <- List(
  Array(0),
  Array(1, 0),
  Array(0, 1, 0),
  Array(1, 1, 0),
  Array(2, 3, 7, 15),
  Array("hello", 1, 30),
)) {
  val result:String = arr match {
    case Array(0) => "0"
    case Array(1, 0) => "Array(1, 0)"
    case Array(x, y) => "Array: " + x + ", " + y    // 匹配两元素数组
    case Array(0, _*) => "以0开头的数组"
    case Array(x, 1, z) => "中间为1的三元素数组"
    case _ => "something else"
  }
  println(result)

2.4 匹配列表

    for (list <- List(
      List(0),
      List(1, 0),
      List(0, 0, 0),
      List(1, 1, 0),
      List(88),
      List("hello")
    )) {
      val result:String = list match {
        case List(0) => "0"
        case List(x, y) => "List(x, y): " + x + ", " + y
        case List(0, _*) => "List(0, ...)"
        case List(a) => "List(a): " + a
        case _ => "something else"
      }
      println(result)
    }

2.5 匹配元组

    for (tuple <- List(
      (0, 1),
      (0, 0),
      (0, 1, 0),
      (0, 1, 1),
      (1, 23, 56),
      ("hello", true, 0.5)
    )){
      val result = tuple match {
        case (a, b) => "" + a + ", " + b
        case (0, _) => "(0, _)"
        case (a, 1, _) => "(a, 1, _) " + a
        case (x, y, z) => "(x, y, z) " + x + " " + y + " " + z
        case _ => "something else"
      }
      println(result)

2.6 通用匹配

这种方式很像python的接收参数的用法

package com.lrm.demo08

/**
 * @author RuiMing Lin 
 * @date 2021-06-05 10:06
 * @description 元祖匹配
 */
object demo3 {
  def main(args: Array[String]): Unit = {
    // 1. 在变量声明时匹配
    val (x, y) = (10, "hello")
    println(s"x: $x, y: $y")

    val List(first, second, _*) = List(23, 15, 9, 78)
    println(s"first: $first, second: $second")

    val fir :: sec :: rest = List(23, 15 , 9, 78)
    println(s"first: $fir, second: $sec, rest: $rest")
    println("=====================")

    // 2. for推导式中进行模式匹配
    val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
    // 2.1 原本的遍历方式
    for (elem <- list){
      println(elem._1 + " " + elem._2)
    }
    println("-----------------------")

    // 2.2 将List的元素直接定义为元组,对变量赋值
    for ((word, count) <- list ){
      println(word + ": " + count)
    }
    println("-----------------------")

    // 2.3 可以不考虑某个位置的变量,只遍历key或者value
    for ((word, _) <- list)
      println(word)
    println("-----------------------")

    // 2.4 可以指定某个位置的值必须是多少
    for (("a", count) <- list){
      println(count)
    }
  }
}

3 对象匹配

3.1 unapply

做对象的模式匹配时,必须实现一个unapply方法,用来对对象属性进行拆解

package com.lrm.demo08

/**
 * @author RuiMing Lin 
 * @date 2021-06-05 10:07
 * @description
 */
object demo4 {
  def main(args: Array[String]): Unit = {
    val student = new Student("alice", 19)
    // 针对对象实例的内容进行匹配
    val result = student match {
      case Student("alice", 18) => "Alice, 18"
      case _ => "Else"
    }
    println(result)
  }
}

class Student(val name: String, val age: Int)

// 定义伴生对象
object Student {
  def apply(name: String, age: Int): Student = new Student(name, age)
  // 必须实现一个unapply方法,用来对对象属性进行拆解
  def unapply(student: Student): Option[(String, Int)] = {
    if (student == null){
      None
    } else {
      Some((student.name, student.age))
    }
  }
}

3.2 样例类

case类是特殊的类,可以使用case表达式来进行模式匹配。case类很简洁,并且容易创建,它将其构造参数都公开为值。可以使用case类来创建轻量级值对象,或者类名和属性名都富有意义的数据持有者。

package com.lrm.demo08

/**
 * @author RuiMing Lin 
 * @date 2021-06-05 10:07
 * @description 样例类
 */
object demo5 {
  def main(args: Array[String]): Unit = {
    val student = Student1("alice", 18)

    // 针对对象实例的内容进行匹配
    val result = student match {
      case Student1("alice", 18) => "Alice, 18"
      case _ => "Else"
    }
    println(result)
  }
}

// 定义样例类
case class Student1(name: String, age: Int)

以上是关于【Scala】模式匹配和样本类的主要内容,如果未能解决你的问题,请参考以下文章

样本类和模式匹配

Scala笔记--模式匹配

Scala笔记--模式匹配

Scala 学习 -- 样例类和模式匹配

Scala 学习 -- 样例类和模式匹配

Scala:样例类模式匹配Option偏函数泛型