Scala模式匹配

[toc]

## Scala模式匹配

> Scala中的模式匹配`类似于Java中的switch语法`,但是更加强大。
>
> 模式匹配语法中,采用==match关键字声明==,每个分支==采用case关键字==进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行`case _ 分支`,类似于Java中default语句。

### 1. Scala模式匹配快速入门

~~~~scala
/**
* @Date 2021/4/3 14:55
* @Version 10.21
* @Author DuanChaojie
*/
object MatchDemo01 {
def main(args: Array[String]): Unit = {
val oper = '+'
val n1 = 20
val n2 = 10
var res = 0

/**
* 说明
* 1. match (类似java switch) 和 case 是关键字
* 2. 如果匹配成功, 则执行 => 后面的代码块.
* 3. 匹配的顺序是从上到下,匹配到一个就执行对应的 代码
* 4. => 后面的代码块 不要写 break,会自动的退出match
* 5. 如果一个都没有匹配到,则执行 case _ 后面的代码块
*/
oper match {
case '+' => res = n1 + n2
case '-' => res = n1 - n2
case '*' => res = n1 * n2
case '/' => res = n1 / n2
case '%' => res = n1 % n2
case _ => println("oper error")
}

println("res = " + res)
}
}
~~~~

> match的细节和注意事项:
>
> 1. 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
> 2. 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError
> 3. 每个case中,不用break语句,自动中断case
> 4. 可以在match中使用其它类型,而不 仅仅是字符
> 5. `=>` 等价于 java swtich 的 `:`
> 6. `=>` 后面的代码块到下一个 case, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩。

#### 条件守卫

如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。

```scala
/**
* @Date 2021/4/3 15:21
* @Version 10.21
* @Author DuanChaojie
*/
object MatchIfDemo01 {
def main(args: Array[String]): Unit = {
/**
* 是对"+-3!" 遍历
*/
for (ch <- "+-3!") {

var sign = 0
var digit = 0
// 模式匹配
ch match {
case '+' => sign = 1
case '-' => sign = -1

/**
* 如果 case 后有 条件守卫即if ,那么这时的 _ 不是表示默认匹配
* 表示忽略传入的 ch
*/
case _ if ch.toString.equals("3") => digit = 3
case _ if (ch > 1110 || ch < 120) => println("ch > 10")
case _ => sign = 2
}

/**
* 分析
* + 1 0
* - -1 0
* 3 0 3
* ! 2 0
* ch + "" + sign + " " + digit
*/
println(s"\nch = $ch \nsign = $sign \ndigit = $digit")
}
}
}
```

#### 课堂练习

> 练习一:

~~~~scala
/**
* @Date 2021/4/3 15:40
* @Version 10.21
* @Author DuanChaojie
*/
object MatchExercise01 {
def main(args: Array[String]): Unit = {

for (ch <- "+-3!") {

var sign = 0
var digit = 0

ch match {
case '+' => sign = 1
case '-' => sign = -1
// 可以有多个 默认匹配,但是后面的默认匹配无效,编译器没有报错
case _ => digit = 3
case _ => sign = 2
}

/**
* + 1 0
* - -1 0
* 3 0 3
* ! 0 3
*/
println(s"\nch = $ch \nsign = $sign \ndigit = $digit")
}
}
}
~~~~

> 练习二:

~~~~scala
/**
* @Date 2021/4/3 16:00
* @Version 10.21
* @Author DuanChaojie
*/
object MatchExercise02 {
def main(args: Array[String]): Unit = {
for (ch <- "+-3!") {

var sign = 0
var digit = 0

ch match {
case _ if ch > 10000 => digit = 3
case '+' => sign = 1
case '-' => sign = -1
// 说明..
case _ => println("没有任何匹配~~~")
}

/**
* 结果分析
* + 1 0
* - -1 0
* 没有任何匹配~~~ 3 0 0
* 没有任何匹配~~~ ! 0 0
*/
println(s"\nch = $ch \nsign = $sign \ndigit = $digit")
}
}
}
~~~~

#### 模式中的变量

> 如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量

~~~~scala
/**
* @Date 2021/4/3 16:05
* @Version 10.21
* @Author DuanChaojie
*/
object MatchVarDemo01 {
def main(args: Array[String]): Unit = {
val ch = 'U'
// 模式匹配
ch match {
case '+' => println("ok~")
// 下面 case mychar 含义是 mychar = ch
case mychar => println("ok~" + mychar)
case _ => println("ok~~")
}

val ch1 = '+'
/**
* match是一个表达式,因此可以有返回值
* 返回值就是匹配到的代码块的最后一句话的值
*/
val res = ch1 match {
case '+' => ch1 + " hello "
// 下面 case mychar 含义是 mychar = ch
case _ => println("ok~~")
}

println("res = " + res)
}
}
~~~~

> 变量声明中的模式:match中每一个case都可以单独提取出来,意思是一样的。

~~~~scala
/**
* @Date 2021/4/3 16:52
* @Version 10.21
* @Author DuanChaojie
*/
object MatchVarDemo02 {
def main(args: Array[String]): Unit = {
val (x, y, z) = (1, 2, "Hello")

/**
* x = 1
* y = 2
* z = Hello
*/
println(s"x = $x\ny = $y\nz = $z\n")
/**
* i = BigInt(10)/3
* j = BigInt(10)%3
*/
val (i, j) = BigInt(10) /% 3

/**
* i = 3
* j = 1
*/
println(s"i = $i\nj = $j")
val arr = Array(1, 3, 5, 7, 9)
// 提出 arr 的前两个元素
val Array(first, second, _*) = arr

/**
* first = 1
* second = 3
*/
println(s"first = $first \nsecond = $second")
}
}
~~~~

#### For表达式中的模式

for表达式中的模式

~~~~scala

/**
* @Date 2021/4/3 17:18
* @Version 10.21
* @Author DuanChaojie
*/
object MatchForDemo01 {
def main(args: Array[String]): Unit = {

val map = Map("A" -> 1, "B" -> 0, "C" -> 3)

// 出来三个key-value ("A"->1), ("B"->0), ("C"->3)
for ((k, v) <- map) {
println(k + " -> " + v)
}

/**
* 说明 : 只遍历出 value = 0 的key-value ,其它的过滤掉
*/
println("--------------(k, 0) <- map-------------------")
for ((k, 0) <- map) {
println(k + " --> " + 0)
}

/**
* 说明:这个就是上面代码的另外写法,只是下面的用法灵活和强大
*/
println("--------------(k, v) <- map if v == 0-------------------")
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v)
}
}
}
~~~~

### 2. Scala模式匹配详解

#### 类型匹配

> 可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法

~~~~scala
/**
* @Date 2021/4/3 16:09
* @Version 10.21
* @Author DuanChaojie
*/
object MatchTypeDemo01 {
def main(args: Array[String]): Unit = {
val a = 8
// 说明 obj 实例的类型 根据 a 的值来返回
val obj = if (a == 1) 1
else if (a == 2) "2"
else if (a == 3) BigInt(3)
else if (a == 4) Map("aa" -> 1)
else if (a == 5) Map(1 -> "aa")
else if (a == 6) Array(1, 2, 3)
else if (a == 7) Array("aa", 1)
else if (a == 8) Array("aa")

/**
* 根据 obj的类型来匹配
*/
val result = obj match {
case a: Int => a
case b: Map[String, Int] => "对象是一个字符串-数字的Map集合"
case c: Map[Int, String] => "对象是一个数字-字符串的Map集合"
case d: Array[String] => d //"对象是一个字符串数组"
case e: Array[Int] => "对象是一个数字数组"
case f: BigInt => Int.MaxValue
case y: Float => println("xx")
case _ => "啥也不是"
}

println(result)
}
}
~~~~

> 类型匹配注意事项:
>
> 1. Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
>
> 2. 在进行类型匹配时,编译器会`预先检测是否有可能的匹配`,如果没有则报错。
>
> 3. 一个说明
>
> ~~~~scala
> // case i : Int => i 表示 将 i = obj (其它类推),然后再判断类型
> val result = obj match {
> case i : Int => i
> }
> ~~~~
>
>
>
> 4. 如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。

#### 匹配数组

> 1. Array(0) 匹配只有一个元素且为0的数组。
> 2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等....
> 3. Array(0,_*) 匹配数组以0开始

~~~scala
import scala.collection.mutable.ArrayBuffer

/**
* @Date 2021/4/3 16:14
* @Version 10.21
* @Author DuanChaojie
*/
object MatchArrDemo01 {
def main(args: Array[String]): Unit = {

// 这里arrs仅仅是一个数据
val arrs = Array(
Array(0), Array(1, 0),
Array(0, 1, 0), Array(1, 1, 0),
Array(1, 1, 0, 1)
)

// 遍历数据,arrs总共有五个元素
for (arr <- arrs) {
// 开始模式匹配
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + " = " + y
case Array(0, _*) => "以0开头和数组"
case _ => "什么集合都不是"
}

/** 对结果分析:
* result = 0
* result = 1 = 0
* result = 以0开头和数组
* result = 什么集合都不是
* result = 什么集合都不是
*/
println("result = " + result)
}

println("--------------------匹配数组练习-------------------------")
/**
* 给你一个数组集合,如果该数组时 Array(10,20) , 请使用默认匹配,返回Array(20,10)
*/
val arrs2 = Array(
Array(0),
Array(1, 0),
Array(0, 1, 0), Array(1, 1, 0),
Array(1, 1, 0, 1)
)

for (arr <- arrs2) {
val result = arr match {
case Array(x, y) => ArrayBuffer(y, x) //Array(y,x).toBuffer //? ArrayB(y,x)
case _ => "不处理~~"
}

println("result = " + result) //ArrayBuffer(0,1)
}
}
}
~~~

#### 匹配列表

~~~~scala
/**
* @Date 2021/4/3 16:21
* @Version 10.21
* @Author DuanChaojie
*/
object MatchListDemo01 {
def main(args: Array[String]): Unit = {
val array = Array(
List(0),
List(1, 0),
List(88),
List(0, 0, 0), List(1, 0, 0)
)

for (list <- array) {

// 进行模式匹配
val result = list match {
case 0 :: Nil => "0" //
case x :: y :: Nil => x + " " + y

// "以0开头的数组"
case 0 :: tail => "0 ..." //
case x :: Nil => x
case _ => "something else"
}

/**
* result = 0
* result = 1 0
* result = 88
* result = 0 ...
* result = something else
*/
println("result = " + result)
}
}
}
~~~~

#### 匹配元组

~~~~scala
/**
* @Date 2021/4/3 16:27
* @Version 10.21
* @Author DuanChaojie
*/
object MatchTupleDemo01 {
def main(args: Array[String]): Unit = {
val array = Array((0, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))

/**
* 如果要匹配 (10, 30) 这样任意两个元素的对偶元组,应该如何写
*/
for (pair <- array) {
// 开始模式匹配
val result = pair match {
case (0, _) => "0 ..."
case (y, 0) => y
case (x, y) => (y, x) //"匹配到(x,y)" + x + " " + y
case _ => "other"
}

/**
* result = 0 ...
* result = 1
* result = (30,10)
* result = (1,1)
* result = other
*/
println("result = " + result)
}
}
}
~~~~

#### 匹配对象

> 对象匹配,什么才算是匹配呢?规则如下:
>
> case中对象的unapply方法(对象提取器)返回Some集合则为匹配成功,返回none集合则为匹配失败。
>
> 快速入门案例:

~~~~scala
/**
* @Date 2021/4/3 16:32
* @Version 10.21
* @Author DuanChaojie
*/
object MatchObjectDemo01 {
def main(args: Array[String]): Unit = {
val number: Double = Square(6.0)
println(number)
number match {
/**
* 说明 case Square(arg) 的运行的机制
* 1. 当匹配到 case Square(arg)
* 2. 调用Square 的 unapply(arg: Double),arg的值就是 number
* 3. 如果对象提取器 unapply(arg: Double) 返回的是Some(6.0) ,
* 则表示匹配成功,同时将6.0 赋给 Square(arg) 的 arg
* 4. 果对象提取器 unapply(arg: Double) 返回的是None ,则表示匹配不成功
*/
case Square(arg) => println(s"匹配成功arg = $arg")
case _ => println("nothing matched")
}
}
}

object Square {
/**
* unapply方法是对象提取器
*
* @param arg 接收的Double类型的参数
* @return 返回类型是Option[Double],
* 返回值是Some(math.sqrt(arg)),返回arg的开平方的值并放入到Some()中
*/
def unapply(arg: Double): Option[Double] = {
Some(math.sqrt(arg))
}

def apply(arg: Double): Double = arg * arg
}

~~~~

> 应用案例二:

~~~scala
/**
* @Date 2021/4/3 16:48
* @Version 10.21
* @Author DuanChaojie
*/
object MatchObjectDemo2 {
def main(args: Array[String]): Unit = {

val namesString = "Alice,Bob,Thomas"
// 开始模式匹配
namesString match {
/**
* 当执行case Names(first, second, third)
* 1. 会调用 unapplySeq(str),把 "Alice,Bob,Thomas" 传入给 str
* 2. 如果 返回的是 Some("Alice","Bob","Thomas"),分别给 (first, second, third)
* 注意,这里的返回的值的个数需要和 (first, second, third)要一样
* 3. 如果返回的None ,表示匹配失败
*/
case Names(first, second, third) => {
// the string contains three people's names
// Alice Bob Thomas
println("the string contains three people's names")
// 打印字符串
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
}
}

object Names {
//当构造器是多个参数时,就会触发这个对象提取器
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) {
Some(str.split(","))
} else {
None
}
}
}
~~~

> 1. 当case 后面的对象提取器方法的参数为多个,则会默认调用`def unapplySeq()` 方法
> 2. 如果`unapplySeq`返回是Some,获取其中的值,判断得到的sequence中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first,second和third
> 3. 其它的规则不变。

### 3. 样例类

> 1. 样例类仍然是类
> 2. 样例类用case关键字进行声明。
> 3. `样例类是为模式匹配而优化的类`。
> 4. 构造器中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做)
> 5. 在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象提供unapply方法让模式匹配可以工作
> 6. 将自动生成toString、equals、hashCode和copy方法(有点类似模板类,直接给生成,供程序员使用)
> 7. 除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们。

#### 样例类快速入门

~~~~scala
package com.atguigu.chapter12.caseclass

/**
* @Date 2021/4/3 17:23
* @Version 10.21
* @Author DuanChaojie
*/
object CaseClassDemo01 {
def main(args: Array[String]): Unit = {
println("-------------------------------")

/**
* 类型(对象)--序列化---字符串:
* 1.可以保存到文件中
* 2.反序列化
* 3.网络传输
*/
}
}

abstract class Amount

/**
* 样例类
* @param value
*/
case class Dollar(value: Double) extends Amount

/**
* 样例类
* @param value
* @param unit
*/
case class Currency(value: Double, unit: String) extends Amount

/**
* 样例类
*/
case object NoAmount extends Amount
~~~~

#### 样例类最佳实践

> 样例类最佳实践一:当我们有一个类型为Amount的对象时,可以用模式匹配来匹配他的类型,并将属性值绑定到变量(即:把样例类对象的属性值提取到某个变量,该功能有用)

~~~~scala
package com.atguigu.chapter12.caseclass

/**
* @Date 2021/4/3 18:15
* @Version 10.21
* @Author DuanChaojie
*/
object CaseClassDemo02 {
def main(args: Array[String]): Unit = {
val array = Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)
for (amt <- array) {
val result = amt match {
case Dollar2(v) => "$" + v
case Currency2(v, u) => v + " " + u
case NoAmount2 => ""
}
println("amt = " + amt)
println("result =" + result)
}
}
}

abstract class Amount2

/**
* 样例类
*
* @param value
*/
case class Dollar2(value: Double) extends Amount2

/**
* 样例类
*
* @param value
* @param unit
*/
case class Currency2(value: Double, unit: String) extends Amount2

/**
* 样例类
*/
case object NoAmount2 extends Amount2
~~~~

> 样例类最佳实践二:
>
> 1. 样例类的copy方法和带名参数
> 2. copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。

~~~~scala
/**
* @Date 2021/4/3 18:30
* @Version 10.21
* @Author DuanChaojie
*/
object CaseClassDemo03 {
def main(args: Array[String]): Unit = {
val amt1 = new Currency3(3000.0, "RMB")
// 克隆,创建的对象和amt的属性一样
val amt2 = amt1.copy()

// amt2.value = Currency3(3000.0,RMB).value amt2.unit = Currency3(3000.0,RMB).unit
println(s"amt2.value = $amt2.value \t amt2.unit = $amt2.unit")

val amt3 = amt1.copy(value = 8000.0)
// amt3 = Currency3(8000.0,RMB)
println("amt3 = " + amt3)

val amt4 = amt1.copy(unit = "美元")
// amt4 = Currency3(3000.0,美元)
println("amt4 = " + amt4)
}
}

abstract class Amount3

/**
* 样例类
*
* @param value
*/
case class Dollar3(value: Double) extends Amount3

/**
* 样例类
*
* @param value
* @param unit
*/
case class Currency3(value: Double, unit: String) extends Amount3

/**
* 样例类
*/
case object NoAmount3 extends Amount3
~~~~

#### case语句的中置(缀)表达式

> 什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中使用中置表示法。比如可以匹配一个List序列

~~~~scala
/**
* @Date 2021/4/3 18:38
* @Version 10.21
* @Author DuanChaojie
*/
object MidCase {
def main(args: Array[String]): Unit = {
List(1, 3, 5, 9) match {
/**
* 修改并测试
* 1.两个元素间::叫中置表达式,至少first,second两个匹配才行.
* 2.first 匹配第一个 second 匹配第二个, rest 匹配剩余部分(5,9)
*/
case first :: second :: rest => println(first + " " + second + "\nrest.length = "+ rest.length + "\n" + rest) //
case _ => println("匹配不到...")
}
}
}
~~~~

#### 匹配嵌套结构

> 操作原理类似于正则表达式
>
> 最佳实践案例——商品捆绑打折出售
>
> 1. 现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。
> 2. 要求商品捆绑可以是单个商品,也可以是多个商品。
> 3. 打折时按照折扣x元进行设计
> 4. 能够统计出所有捆绑商品打折后的最终价格。
>
> 样例类的设计:

~~~~scala
/**
* 设计样例类
*/
abstract sealed class Item

case class Book(description: String, price: Double) extends Item

case class Food(description: String, price: Double) extends Item

/**
* Bundle 捆 , discount: Double 折扣 , item: Item*
*
* @param description 描述
* @param discount 折扣的价格
* @param item
*/
case class Bundle(description: String, discount: Double, item: Item*) extends Item
~~~~

> 完成这个案例需要有以下知识储备:
>
> 1. 如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
> 2. 通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest
> 3. 不使用_*绑定剩余Item到rest

~~~~scala
/**
* @Date 2021/4/3 18:43
* @Version 10.21
* @Author DuanChaojie
*/
object CaseClassExercise {
def main(args: Array[String]): Unit = {
/**
* 这里给出了一个具体的打折的案例
*/
val sale = Bundle("书籍", 10, Book("漫画", 40),
Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30), Book("天龙八部", 100)))

/** 完成上面例子之前需要学习三个知识点:
* 知识点1 :使用case语句,得到 "漫画"
*/
val res1 = sale match {
/**
* 如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
*/
case Bundle(_, _, Book(desc, _), _*) => desc
}
// res1 = 漫画
println("res1 = " + res1)

/**
* 知识点2-通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest
*/
val res2 = sale match {
//如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
case Bundle(_, _, art@Book(_, _), rest@_*) => (art, rest)
}
// res2 = (Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0), Book(天龙八部,100.0)))))
println("res2 = " + res2)


/**
* 知识点3-不使用_*绑定剩余Item到rest
*/
val res3 = sale match {
// 如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
case Bundle(_, _, art3@Book(_, _), rest3) => (art3, rest3)
}
// res3 = (Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0), Book(天龙八部,100.0))))
println("res3 = " + res3)
}
}
~~~~

> 完成上面的案例:

~~~scala
/**
* @Date 2021/4/3 18:43
* @Version 10.21
* @Author DuanChaojie
*/
object CaseClassExercise {
def main(args: Array[String]): Unit = {
/**
* 这里给出了一个具体的打折的案例
*/
val sale = Bundle("书籍", 10, Book("漫画", 40),
Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30), Book("天龙八部", 100)))

/**
* 完成案例
* price(sale) = 220.0
*/
println("price(sale) = " + price(sale))

}

def price(it: Item): Double = {
it match {
case Book(_, p) => p
case Bundle(_, disc, its@_*) => its.map(price).sum - disc
}
}
}
~~~

### 4. 密闭类

> 1. 如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。
> 2. 密封就是不能在其他文件中定义子类。
> 3. ![image-20210403200246976](assets/image-20210403200246976.png)

## ☆

原文地址:https://www.cnblogs.com/huaobin/p/15764430.html