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 n 1 = 20 val n 2 = 10 var res = 0 oper match { case '+' = > res = n 1 + n 2 case '-' = > res = n 1 - n 2 case '*' = > res = n 1 * n 2 case '/' = > res = n 1 / n 2 case _ = > println( "oper error" ) } println( "res=" + res) } } |
match在匹配上的细节和注意事项
- 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
- 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError
- 每个case中,不用break语句,自动中断case
- 可以在match中使用其它类型,而不仅仅是字符
- => 等价于 java swtich 的 :
- => 后面的代码块到下一个 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) } } |
类型匹配中注意事项
- Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
- 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.
- 说明:val result = obj match { case i : Int => i } “case i : Int => i ”表示 将 i = obj (obj赋值给i),然后再判断类型
- 如果 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字节码
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 52 53 | public class Currency extends Amount implements Product, Serializable { private final double value; private final String unit; public static Option<Tuple2<Object, String>> unapply(Currency paramCurrency) { return Currency..MODULE$.unapply(paramCurrency); } public static Currency apply( double paramDouble, String paramString) { return Currency..MODULE$.apply(paramDouble, paramString); } public static Function1<Tuple2<Object, String>, Currency> tupled() { return Currency..MODULE$.tupled(); } public static Function1<Object, Function1<String, Currency>> curried() { return Currency..MODULE$.curried(); } public double value() { return this .value; } public String unit() { return this .unit; } public Currency copy( double value, String unit) { return new Currency(value, unit); } public double copy$ default $ 1 () { return value(); } public String copy$ default $ 2 () { return unit(); } public String productPrefix() { return "Currency" ; } public int productArity() { return 2 ; } public Object productElement( int x$ 1 ) { int i = x$ 1 ; switch (i) { default : throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(x$ 1 ).toString()); case 1 : break ; case 0 : } return BoxesRunTime.boxToDouble(value()); } public Iterator<Object> productIterator() { return ScalaRunTime..MODULE$.typedProductIterator( this ); } public boolean canEqual(Object x$ 1 ) { return x$ 1 instanceof Currency; } public int hashCode() { int i = - 889275714 ; i = Statics.mix(i, Statics.doubleHash(value())); i = Statics.mix(i, Statics.anyHash(unit())); return Statics.finalizeHash(i, 2 ); } public String toString() { return ScalaRunTime..MODULE$._toString( this ); } public boolean equals(Object x$ 1 ) { if ( this != x$ 1 ) { Object localObject = x$ 1 ; int i; if ((localObject instanceof Currency)) i = 1 ; else i = 0 ; if (i == 0 ) break label97; Currency localCurrency = (Currency)x$ 1 ; if (value() == localCurrency.value()) { str = localCurrency.unit(); String tmp55_45 = unit(); if (tmp55_45 == null ) { tmp55_45; if (str == null ) break label76; tmpTernaryOp = tmp55_45; break label89; } } } } public Currency( double value, String unit) { Product. class .$init$( this ); } } |
Currency$.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public final class Currency$ extends AbstractFunction2<Object, String, Currency> implements Serializable { public static final MODULE$; static { new (); } public final String toString() { return "Currency" ; } public Currency apply( double value, String unit) { return new Currency(value, unit); } public Option<Tuple2<Object, String>> unapply(Currency x$ 0 ) { return x$ 0 == null ? None..MODULE$ : new Some( new Tuple2(BoxesRunTime.boxToDouble(x$ 0 .value()), x$ 0 .unit())); } private Object readResolve() { return MODULE$; } private Currency$() { MODULE$ = this ; } } |
样例类的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 result 2 = sale match { case Bundle( _ , _ , art @ Book( _ , _ ), rest @ _ *) = > (art, rest) } println(result 2 ) println( "art =" + result 2 . _ 1 ) println( "rest=" + result 2 . _ 2 ) |
知识点3-不使用_*绑定剩余Item到rest
请思考:如何将 "漫画" 和 紫色部分 val sale = Bundle("书籍", 10, Article("漫画", 40), Bundle("文学作品", 20, Article("《阳关》", 80), Article("《围城》", 30))) 这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中.
1 2 3 4 5 6 7 | val result 2 = sale match { //说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。 case Bundle( _ , _ , art @ Book( _ , _ ), rest) = > (art, rest) } println(result 2 ) println( "art =" + result 2 . _ 1 ) println( "rest=" + result 2 . _ 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 SalesDem 0 { 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 res 2 = sale match { //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有 case Bundle( _ , _ , art @ Book( _ , _ ), rest @ _ *) = > (art, rest) } println( "res2=" + res 2 ) //知识点3-不使用_*绑定剩余Item到rest val res 3 = sale match { //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有 case Bundle( _ , _ , art 3 @ Book( _ , _ ), rest 3 ) = > (art 3 , rest 3 ) } println( "res3=" + res 3 ) //案例的完成 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 = 漫画 res 2 = (Book(漫画, 40.0 ),WrappedArray(Bundle(文学作品, 20.0 ,WrappedArray(Book(《阳关》, 80.0 ), Book(《围城》, 30.0 ), Book(天龙八部, 100.0 ))))) res 3 = (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
scala:Match的匹配条件有多种,可以是基本数据类型,也可以是引用数据类型(对象,数组,元组,列表等),甚至可以进行类型匹配。
(2)关于默认匹配上的差异
Java:在switch结果中,即便不存在default,也不会报错,并且default可以置于结构的首行,而不影响执行顺序。在switch结构中,defaul只是case都匹配不到时,才执行它。
Scala:在Match中,如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError;默认匹配的顺序,影响case语句的执行顺序,当放到match的首行时,后面的case匹配不生效。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于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保姆级教程