Scala中的模式匹配

 

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

1.匹配基本类型

1.观察如下实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object Demo_037 {
  def main(args: Array[String]): Unit = {
    val oper = '#'
    val n1 = 20
    val n2 = 10
    var res = 0
    oper match {
      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, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩。

2.如果存在多个默认匹配的情况,则只有一个会生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object Demo_039 {
  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
      }
      println(ch + " " + sign + " " + digit)
    }
  }
}

 

3. 默认匹配放到match的首行时,只会执行默认匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object Demo_040 {
  def main(args: Array[String]): Unit = {
    for (ch <- "+-3!") {
      var sign = 0
      var digit = 0
      ch match {
          //默认匹配放到match的首行时,后面的匹配不生效
        case _  => digit = 3
        case '+' => sign = 1
        case '-' => sign = -1
      }
      println(ch + " " + sign + " " + digit)
    }
  }
}

  输出结果

 

2.守卫

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object Demo_038 {
  def main(args: Array[String]): Unit = {
    for (ch <- "+-3!") {
      var sign = 0
      var digit = 0
      ch match {
        case '+' => sign = 1
        case '-' => sign = -1
        //case _ 后出现守卫条件则不表示默认匹配,表示的是忽略所传入的char
        case _ if ch.toString.equals("3") => digit = 3
        case _ => sign = 2  //默认匹配
      }
      println(ch + " " + sign + " " + digit)
    }
  }
}

  输出结果为

3.模式中的变量

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

1
2
3
4
5
6
7
8
9
10
object Demo_041 {
  def main(args: Array[String]): Unit = {
    val ch = 'V'
    ch match {
      case '+' => println("other~")
      case mychar => println("wise~" + mychar)
      case _ => println ("say~~")
    }
  }
}

  输出结果

 

扩展:在Spark中经常会使用到模式匹配,如下面在Spark中的一段程序,它使用的就是模式中的变量,分别将值赋给id,name和age,然后再传入到样例类Emp中

 

 

 

4.类型匹配

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

   实例:根据输入的数值,匹配不同类型的数据

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 Demo_041 {
  def main(args: Array[String]): Unit = {
    // 类型匹配, obj 可能有如下的类型
    val a = 7
    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")
 
    val result = obj match {
      case a : Int => a
      case b : Map[String, Int] => "对象是一个字符串-数字的Map集合"
      case c : Map[Int, String] => "对象是一个数字-字符串的Map集合"
      case d : Array[String] => "对象是一个字符串数组"
      case e : Array[Int] => "对象是一个数字数组"
      case f : BigInt => Int.MaxValue
      case _ => "啥也不是"
    }
    println(result)
 
  }
} 

   类型匹配中注意事项

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

5.匹配数组

1.Array(0) 匹配只有一个元素且为0的数组。

2.Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等....

3.Array(0,_*) 匹配数组以0开始

实例:匹配不同元素的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object Demo_042 {
  def main(args: Array[String]): Unit = {
    for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0),
      Array(1, 1, 0), Array(1, 1, 0, 1))) {
      val result = arr match {
        case Array(0) => "0"
        case Array(x, y) => x + "=" + y
        case Array(0, _*) => "以0开头和数组"
        case _ => "什么集合都不是"
      }
      println("result = " + result)
    }
  }
}

  输出结果

6.匹配列表

实例:匹配各个列表

1
2
3
4
5
6
7
8
9
10
11
12
13
object Demo_043 {
  def main(args: Array[String]): Unit = {
    for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
      val result = list match {
        case 0 :: Nil => "0" //匹配只有一个元素,且元素为0的列表
        case x :: y :: Nil => x + " " + y //匹配有两个元素的列表,列表内元素任意
        case 0 :: tail => "0 ..."
        case _ => "something else"
      }
      println(result)
    }
  }
}

  运行结果

7.匹配元组

 实例:匹配元组

1
2
3
4
5
6
7
8
9
10
11
12
13
object Demo_044 {
  def main(args: Array[String]): Unit = {
    // 元组匹配
    for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) {
      val result = pair match { //
        case (0, _) => "0 ..." //
        case (y, 0) => y //
        case _ => "other" //.
      }
      println(result)
    }
  }
}

  运行结果

8.对象匹配

对象匹配,什么才算是匹配呢?

规则如下:

  • case中对象的unapply方法(对象提取器)返回Some集合则为匹配成功
  • 返回none集合则为匹配失败

实例:求平方根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object Demo_045 {
  def main(args: Array[String]): Unit = {
    object Square {
      def unapply(z: Double): Option[Double] = Some(math.sqrt(z))//如果返回的some中有值,则表示匹配成功,否则匹配失败
      def apply(z: Double): Double = z * z
    }
    // 模式匹配使用:
    val number: Double = Square(36.0)
    number match {
      case Square(n) => println(n)
      case _ => println("nothing matched")
    }
  }
}

 

 对象匹配运行机制说明

实例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
object Demo_046 {
  def main(args: Array[String]): Unit = {
    object Names {
      def unapplySeq(str: String): Option[Seq[String]] = {
        if (str.contains(",")) Some(str.split(","))
        else None
      }
    }
    val namesString = "Alice,Bob,Thomas"
    //说明
    namesString match {
      case Names(first, second, third) => {
        println("the string contains three people's names")
        // 打印字符串
        println(s"$first $second $third")
      }
      case _ => println("nothing matched")
    }
  }
}

  运行结果

 程序执行过程分析

 

当case 后面的对象提取器方法的参数为多个,则会默认调用def unapplySeq() 方法

如果unapplySeq返回是Some,获取其中的值,判断得到的sequence中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first,second和third

其它的规则不变.

9.变量声明中的模式

match中每一个case都可以单独提取出来,意思是一样的

object Demo_047 {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
val (q, r) = BigInt(10) /% 3 //说明 q = BigInt(10) / 3,r = BigInt(10) % 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr // 提出arr的前两个元素
println(x,y)
println(q,r)
println(first, second)

}
}

  输出结果

 

10.for表达式中的模式匹配

    for循环也可以进行模式匹配

 实例:使用模式匹配过滤Map值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object Demo_048 {
  def main(args: Array[String]): Unit = {
    val map = Map("A"->1, "B"->0, "C"->3)
    for ( (k, v) <- map ) {
      print(k + " -> " + v+"\t")
    }
    println()
    //说明只变量出value=0的(key,value),其他舍弃
    for ((k, 0) <- map) {
      println(k + " --> " + 0)
    }
    //说明,等同于上面的,只是使用了守卫
    for ((k, v) <- map if v == 0) {
      println(k + " ---> " + v)
    }
  }
} 

11.样例类

 

 定义三个样例类

 

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

实例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object Demo_049 {
  def main(args: Array[String]): Unit = {
    for (amt <- Array(Dollar(1000.0), Currency(1000.0, "RMB"), NoAmount)) {
      val result = amt match {
        //说明
        case Dollar(v) => "$" + v
        //说明
        case Currency(v, u) => v + " " + u
        case NoAmount => ""
      }
      println(amt + ": " + result)
    }
 
  }
}
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount

  输出结果

观察Currency编译后的java字节码 

Currency$.class

  

  

样例类的copy方法和带名参数 copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。

提供了一种拷贝对象的方式: 

    

 

 在main方法中,添加如下代码观察输出结果

1
2
3
4
5
6
7
val amt = Currency(29.95, "RMB")
val amt1 = amt.copy() //创建了一个新的对象,但是属性值一样
val amt2 = amt.copy(value = 19.95) //创建了一个新对象,但是修改了货币单位
val amt3 = amt.copy(unit = "英镑")//..
println(amt)
println(amt2)
println(amt3)

 输出结果

 

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

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

1
2
3
4
5
6
List(1, 3, 5, 9) match { //修改并测试
//1.两个元素间::叫中置表达式,至少first,second两个匹配才行.
//2.first 匹配第一个 second 匹配第二个, rest 匹配剩余部分(5,9)
case first :: second :: rest => println(first + second + rest.length) //
case _ => println("匹配不到...")
}

  

 

13.匹配嵌套结构

基本介绍 :操作原理类似于正则表达式

实践案例-商品捆绑打折出售:

       现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求 商品捆绑可以是单个商品,也可以是多个商品。 打折时按照折扣x元进行设计. 能够统计出所有捆绑商品打折后的最终价格。

创建样例类

1
2
3
4
5
abstract class Item // 项
 
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* ,
case class Bundle(description: String, discount: Double, item: Item*) extends Item

  

 

匹配嵌套结构(就是Bundle的对象)

1
2
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10,  Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))

  

知识点1-将descr绑定到第一个Book的描述

如何取出 val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30))) 这个嵌套结构中的 "漫画"

1
2
3
4
val res = sale match  {
//如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
case Bundle(_, _, Book(desc, _), _*) => desc
}

  

知识点2-通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest

请思考:如何将 "漫画" 和 val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30))) 这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中.

1
2
3
4
5
6
val result2 = sale match {
case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest)
}
println(result2)
println("art =" + result2._1)
println("rest=" + result2._2)

知识点3-不使用_*绑定剩余Item到rest

请思考:如何将 "漫画" 和 紫色部分 val sale = Bundle("书籍", 10, Article("漫画", 40), Bundle("文学作品", 20, Article("《阳关》", 80), Article("《围城》", 30))) 这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中.

 

1
2
3
4
5
6
7
val result2 = sale match {
//说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。
case Bundle(_, _, art @ Book(_, _), rest) => (art, rest)
}
println(result2)
println("art =" + result2._1)
println("rest=" + result2._2)

 

实践案例-商品捆绑打折出售

现在有一些商品,请使用Scala设计相关的样例类,完成商品可以捆绑打折出售。要求

(1)商品捆绑可以是单个商品,也可以是多个商品。

(2)打折时按照折扣xx元进行设计.

(3)能够统计出所有捆绑商品打折后的最终价格

 

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
45
46
47
48
49
50
51
object SalesDem0 {
  def main(args: Array[String]): Unit = {
 
    //这里给出了一个具体的打折的案例
    // 220.
    val sale = Bundle("书籍", 10,  Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30),Book("天龙八部", 100)))
 
    //知识点1 - 使用case语句,得到 "漫画"
    val res = sale match  {
      //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
      case Bundle(_, _, Book(desc, _), _*) => desc
    }
    println("res=" + res) //
 
 
    //知识点2-通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest
 
    val res2 = sale match  {
      //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
      case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest)
    }
    println("res2=" + res2)
 
    //知识点3-不使用_*绑定剩余Item到rest
 
    val res3 = sale match  {
      //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
      case Bundle(_, _, art3 @ Book(_, _), rest3) => (art3, rest3)
    }
    println("res3=" + res3)
 
    //案例的完成
    println("price=" + price(sale)) // 120
  }
 
  def price(it:Item): Double = {
    it match  {
      case Book(_,p) => p
      case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
 
    }
  }
}
 
//设计样例类
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* ,
case class Bundle(description: String, discount: Double, item: Item*) extends Item  

 

运行结果:

1
2
3
4
res=漫画
res2=(Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0), Book(天龙八部,100.0)))))
res3=(Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0), Book(天龙八部,100.0))))
price=220.0

  

 

14.密封类

(1)如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。

(2)密封就是不能在其他文件中定义子类。

 

 

 

 

 

 

15.scala中的match和java中的switch比较

(1)匹配条件上

 java:在switch结构中,case能够匹配到的类型有四中,分别为:char、byte、short或int

6865bda2083d8ddc7adc9bff4bf60867.png

scala:Match的匹配条件有多种,可以是基本数据类型,也可以是引用数据类型(对象,数组,元组,列表等),甚至可以进行类型匹配。


(2)关于默认匹配上的差异

 Java:在switch结果中,即便不存在default,也不会报错,并且default可以置于结构的首行,而不影响执行顺序。在switch结构中,defaul只是case都匹配不到时,才执行它。

 Scala:在Match中,如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError;默认匹配的顺序,影响case语句的执行顺序,当放到match的首行时,后面的case匹配不生效。

 



如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   cosmoswong  阅读(1648)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示