第 8 章 模式匹配

Scala 中的模式匹配类似于 Java 中的 switch 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
int i = 10
switch (i) {
case 10 :
System.out.println("10");
break;
case 20 :
System.out.println("20");
break;
default :
System.out.println("other number");
break;
}

但是 scala 从语法中补充了更多的功能,所以更加强大。

8.1 基本语法

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

1)说明

  • 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句, 若此时没有 case _ 分支,那么会抛出 MatchError。
  • 每个 case 中,不需要使用 break 语句,自动中断 case。
  • match case 语句可以匹配任何类型,而不只是字面量。
  • => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以 使用{}括起来,也可以不括。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
object Test01_PatternMatchBase {
def main(args: Array[String]): Unit = {
// 1. 基本定义语法
val x: Int = 5
val y: String = x match {
case 1 => "one"
case 2 => "two"
case 3 => "three"
case _ => "other"
}
println(y)

// 2. 示例:用模式匹配实现简单二元运算
val a = 25
val b = 13

def matchDualOp(op: Char): Int = op match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case '%' => a % b
case _ => -1
}
println(matchDualOp('+'))
println(matchDualOp('/'))
println(matchDualOp('\\'))

}
}

8.2 模式守卫

1)说明

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

2)案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object Test01_PatternMatchBase {
def main(args: Array[String]): Unit = {


// 3. 模式守卫
// 求一个整数的绝对值
def abs(num: Int): Int = {
num match {
case i if i >= 0 => i
case i if i < 0 => -i
}
}

println(abs(67))
println(abs(0))
println(abs(-24))
}
}

image-20210405233233361

8.3 模式匹配类型

8.3.1 匹配常量

1)说明

Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。

2)代码实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
// 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))


}
}

image-20210405234224509

8.3.2 匹配类型

1)说明

需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使 用模式匹配实现同样的功能。

2)案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {

println("==================================")

// 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))) //泛型擦除 所以List[Int]也可匹配到
println(describeType(Array("hi", "hello"))) // 数组例外,可保留泛型
println(describeType(Array(2, 23)))

}
}

image-20210405233743727

8.3.3 匹配数组

1)说明

scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素 为 0 的数组。

2)案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {
// 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 = 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)
}

}

}

image-20210405233950237

8.3.4 匹配列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {

// 4. 匹配列表
// 方式一
for (list <- List(
List(0),
List(1, 0),
List(0, 0, 0),
List(1, 1, 0),
List(88),
List("hello")
)) {
val result = 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)
}

// 方式二
val list1 = List(1, 2, 5, 7, 24)
val list = List(24)

list match {
case first :: second :: rest => println(s"first: $first, second: $second, rest: $rest")
case _ => println("something else")
}
}
}

image-20210405234046148

8.3.5 匹配元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object Test02_MatchTypes {
def main(args: Array[String]): Unit = {

// 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)
}
}
}

image-20210405234122596

8.3.6 匹配对象及样例类

匹配对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
object Test04_MatchObject {
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))
}
}
}

小结

➢val user = User(“zhangsan”,11),该语句在执行时,实际调用的是 User 伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象。

➢ 当将 User(“zhangsan”, 11)写在 case 后时[case User(“zhangsan”, 11) => “yes”],会默认调用 unapply 方法(对象提取器),user 作为 unapply 方法的参数,unapply 方法将 user 对象的 name 和 age 属性提取出来,与 User(“zhangsan”, 11)中的属性值进行匹配

➢ case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。

➢ 若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]

​ 若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]

​ 若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj):Option[Seq[T]]

样例类

(1)语法:

  • 语法:case class Person (name: String, age: Int)

  • 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 applyunapplytoString、equals、hashCode 和 copy。

  • 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。

  • 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)

上述匹配对象的案例使用样例类会节省大量代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object Test05_MatchCaseClass {
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)

8.3.7 for推导式中变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
object Test03_MatchTupleExtend {
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)
}

// 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)
}
}
}

image-20210406105517276

8.4 偏函数中的模式匹配(了解)

偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式匹配实现的。

1)偏函数定义

1
2
3
val second: PartialFunction[List[Int], Option[Int]] = { 
case x :: y :: _ => Some(y)
}

注:该偏函数的功能是返回输入的List 集合的第二个元素

2)偏函数原理

上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数

检查的函数——isDefinedAt,其返回值类型为 Boolean。

1
2
3
4
5
6
7
8
9
10
11
12
13
val second = new PartialFunction[List[Int], Option[Int]] {
//检查输入参数是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match
{
case x :: y :: _ => true
case _ => false
}
//执行函数逻辑
override def apply(list: List[Int]): Option[Int] = list match
{
case x :: y :: _ => Some(y)
}
}

3)偏函数使用

偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应该调用 applyOrElse 方法,如下

1
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)

applyOrElse 方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法为参数不满足要求的处理逻辑。

4) 案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
object Test06_PartialFunction {
def main(args: Array[String]): Unit = {
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))

// 1. map转换,实现key不变,value2倍
val newList = list.map( tuple => (tuple._1, tuple._2 * 2) )

// 2. 用模式匹配对元组元素赋值,实现功能
val newList2 = list.map(
tuple => {
tuple match {
case (word, count) => (word, count * 2)
}
}
)

// 3. 省略lambda表达式的写法,进行简化 这个是偏函数
val newList3 = list.map {
case (word, count) => (word, count * 2)
}

println(newList)
println(newList2)
println(newList3)

// 偏函数的应用,求绝对值
// 对输入数据分为不同的情形:正、负、0
val positiveAbs: PartialFunction[Int, Int] = {
case x if x > 0 => x
}
val negativeAbs: PartialFunction[Int, Int] = {
case x if x < 0 => -x
}
val zeroAbs: PartialFunction[Int, Int] = {
case 0 => 0
}

def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)

println(abs(-67))
println(abs(35))
println(abs(0))
}
}

将该 List(1,2,3,4,5,6,”test”)中的 Int 类型的元素加一,并去掉字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4,5,6,"test")
val list1 = list.map {
a =>
a match {
case i: Int => i + 1
case s: String =>s + 1
}
}
println(list1.filter(a=>a.isInstanceOf[Int]))
}

方法一:
List(1,2,3,4,5,6,"test").filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int] + 1).foreach(println)
方法二:
List(1, 2, 3, 4, 5, 6, "test").collect { case x: Int => x + 1 }.foreach(println)