news 2026/5/31 13:56:07

Scala核心编程(十二)模式匹配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Scala核心编程(十二)模式匹配

一、match基本介绍

Scala中的模式匹配类似于Java中的switch语法,但是更加强大

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

1.1 Java Switch的简单回顾

// Javainti=1;switch(i){case0:break;case1:break;default:break}

1.2 Scala的模式匹配

// 模式匹配,类似于Java的switch语法valoper='#'valn1=20valn2=10varres=0opermatch{case'+'=>res=n1+n2case'-'=>res=n1-n2case'*'=>res=n1*n2case'/'=>res=n1/n2case_=>println("oper error")}println("res="+res)

1.3 match的细节和注意事项

  1. 如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句
  2. 如果所有case都不匹配,又没有写case _分支,那么会抛出MatchError
  3. 每个case中,不用break语句,自动中断case
  4. 可以在match中使用其它类型,而不仅仅是字符
  5. =>等价于 java switch 的:
  6. =>后面的代码块到下一个 case,是作为一个整体执行,可以使用{}扩起来,也可以不扩。
valoper=1valn1=20valn2=10varres=0opermatch{case'+'=>res=n1+n2case1=>res=n1/n2case_=>println("oper error")}println("res="+res)

二、守卫

2.1 基本介绍

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

2.2 应用案例

for(ch<-"+-3!"){varsign=0vardigit=0chmatch{case'+'=>sign=1case'-'=>sign=-1// 说明..case_ifch.toString.equals("3")=>digit=3case_=>sign=2}println(ch+" "+sign+" "+digit)}

2.3 课堂思考题

如看下面的代码,会输出什么?

代码1:

for(ch<-"+-3!"){varsign=0vardigit=0chmatch{case'+'=>sign=1case'-'=>sign=-1// 说明..case_=>digit=3case_=>sign=2}println(ch+" "+sign+" "+digit)}

代码2:

for(ch<-"+-3!"){varsign=0vardigit=0chmatch{case_=>digit=3case'+'=>sign=1case'-'=>sign=-1// 说明..}println(ch+" "+sign+" "+digit)}

三、模式中的变量

3.1 基本介绍

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

3.2 应用案例

valch='V'chmatch{case'+'=>println("ok~")casemychar=>println("ok~"+mychar)case_=>println("ok~~")}

四、类型匹配

4.1 基本介绍

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

4.2 应用案例

// 类型匹配, obj 可能有如下的类型vala=7valobj=if(a==1)1elseif(a==2)"2"elseif(a==3)BigInt(3)elseif(a==4)Map("aa"->1)elseif(a==5)Map(1->"aa")elseif(a==6)Array(1,2,3)elseif(a==7)Array("aa",1)elseif(a==8)Array("aa")valresult=objmatch{casea:Int=>acaseb:Map[String,Int]=>"对象是一个字符串-数字的Map集合"casec:Map[Int,String]=>"对象是一个数字-字符串的Map集合"cased:Array[String]=>"对象是一个字符串数组"casee:Array[Int]=>"对象是一个数字数组"casef:BigInt=>Int.MaxValuecase_=>"啥也不是"}println(result)

4.3 类型匹配注意事项

  1. Map[String, Int]Map[Int, String]是两种不同的类型,其它类推。
  2. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错。
  3. case i : Int => i表示将i = obj(其它类推),然后再判断类型
  4. 如果case _出现在match中间,则表示隐藏变量名,即不使用,而不是表示默认匹配
valresult=objmatch{casea:Int=>acase_:BigInt=>Int.MaxValue//看这里!caseb:Map[String,Int]=>"对象是一个字符串-数字的Map集合"casec:Map[Int,String]=>"对象是一个数字-字符串的Map集合"cased:Array[String]=>"对象是一个字符串数组"casee:Array[Int]=>"对象是一个数字数组"case_=>"啥也不是"}println(result)

五、匹配数组

5.1 基本介绍

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

5.2 应用案例

for(arr<-Array(Array(0),Array(1,0),Array(0,1,0),Array(1,1,0),Array(1,1,0,1))){valresult=arrmatch{caseArray(0)=>"0"caseArray(x,y)=>x+"="+ycaseArray(0,_*)=>"以0开头和数组"case_=>"什么集合都不是"}println("result = "+result)}

说明:通过增加和删除for循环的数组,来看代码运行的结果,加强对匹配数组的理解。


六、匹配列表

6.1 应用案例

for(list<-Array(List(0),List(1,0),List(0,0,0),List(1,0,0))){valresult=listmatch{case0::Nil=>"0"//casex::y::Nil=>x+" "+y//case0::tail=>"0 ..."//case_=>"something else"}println(result)}

思考题:如果要匹配List(88)这样的只含有一个元素的列表,并原值返回,应该怎么写?


七、匹配元组

7.1 应用案例

// 元组匹配for(pair<-Array((0,1),(1,0),(1,1),(1,0,2))){valresult=pairmatch{//case(0,_)=>"0 ..."//case(y,0)=>y//case_=>"other"//.}println(result)}

思考:如果要匹配(10, 30)这样任意两个元素的对偶元组,应该如何写?


八、对象匹配

8.1 基本介绍

对象匹配,什么才算是匹配呢?规则如下:

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

8.2 应用案例1

objectSquare{defunapply(z:Double):Option[Double]=Some(math.sqrt(z))defapply(z:Double):Double=z*z}// 模式匹配使用:valnumber:Double=36.0numbermatch{caseSquare(n)=>println(n)case_=>println("nothing matched")}

8.3 应用案例1的小结

  1. 构建对象时apply会被调用,比如val n1 = Square(5)
  2. 当将Square(n)写在 case 后时[case Square(n) => xxx],会默认调用unapply方法(对象提取器)
  3. number 会被传递给def unapply(z: Double)的 z 形参
  4. 如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
  5. case中对象的unapply方法(提取器)返回some集合则为匹配成功
  6. 返回none集合则为匹配失败

8.4 应用案例2

objectNames{defunapplySeq(str:String):Option[Seq[String]]={if(str.contains(","))Some(str.split(","))elseNone}}valnamesString="Alice,Bob,Thomas"//说明namesStringmatch{caseNames(first,second,third)=>{println("the string contains three people's names")// 打印字符串println(s"$first$second$third")}case_=>println("nothing matched")}

8.5 应用案例2的小结

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

九、变量声明中的模式

9.1 基本介绍

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

9.2 应用案例

val(x,y)=(1,2)val(q,r)=BigInt(10)/%3//说明 q = BigInt(10) / 3 r = BigInt(10) % 3valarr=Array(1,7,2,9)valArray(first,second,_*)=arr// 提出arr的前两个元素println(first,second)

十、for表达式中的模式

10.1 基本介绍

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

10.2 应用案例

valmap=Map("A"->1,"B"->0,"C"->3)for((k,v)<-map){println(k+" -> "+v)}//说明for((k,0)<-map){println(k+" --> "+0)}//说明for((k,v)<-mapifv==0){println(k+" ---> "+v)}

十一、样例类

11.1 样例类快速入门

abstractclassAmountcaseclassDollar(value:Double)extendsAmountcaseclassCurrency(value:Double,unit:String)extendsAmountcaseobjectNoAmountextendsAmount

说明: 这里的 Dollar,Currency, NoAmount 是样例类。

11.2 基本介绍

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

11.3 样例类最佳实践1

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

for(amt<-Array(Dollar(1000.0),Currency(1000.0,"RMB"),NoAmount)){valresult=amtmatch{//说明caseDollar(v)=>"$"+v//说明caseCurrency(v,u)=>v+" "+ucaseNoAmount=>""}println(amt+": "+result)}

11.4 样例类最佳实践2

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

valamt=Currency(29.95,"RMB")valamt1=amt.copy()//创建了一个新的对象,但是属性值一样valamt2=amt.copy(value=19.95)//创建了一个新对象,但是修改了货币单位valamt3=amt.copy(unit="英镑")//..println(amt)println(amt2)println(amt3)

十二、case语句的中置(缀)表达式

12.1 基本介绍

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

12.2 应用实例

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

十三、匹配嵌套结构

13.1 基本介绍

操作原理类似于正则表达式

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

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

要求:

  1. 商品捆绑可以是单个商品,也可以是多个商品。
  2. 打折时按照折扣x元进行设计。
  3. 能够统计出所有捆绑商品打折后的最终价格

13.3 创建样例类

abstractclassItem// 项caseclassBook(description:String,price:Double)extendsItem//Bundle 捆, discount: Double 折扣, item: Item*,caseclassBundle(description:String,discount:Double,item:Item*)extendsItem

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

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

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

valsale=Bundle("书籍",10,Book("漫画",40),Bundle("文学作品",20,Book("《阳关》",80),Book("《围城》",30)))valres=salematch{//如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有caseBundle(_,_,Book(desc,_),_*)=>desc}

13.6 知识点2-通过@表示法将嵌套的值绑定到变量

valsale=Bundle("书籍",10,Book("漫画",40),Bundle("文学作品",20,Book("《阳关》",80),Book("《围城》",30)))valresult2=salematch{caseBundle(_,_,art @ Book(_,_),rest @ _*)=>(art,rest)}println(result2)println("art ="+result2._1)println("rest="+result2._2)

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

valsale=Bundle("书籍",10,Article("漫画",40),Bundle("文学作品",20,Article("《阳关》",80),Article("《围城》",30)))valresult2=salematch{//说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。caseBundle(_,_,art @ Book(_,_),rest)=>(art,rest)}println(result2)println("art ="+result2._1)println("rest="+result2._2)

13.8 最佳实践案例-商品捆绑打折出售(完整实现)

defprice(it:Item):Double={itmatch{caseBook(_,p)=>p//生成一个新的集合,_是将its中每个循环的元素传递到price中it中。递归操作,分析一个简单的流程caseBundle(_,disc,its @ _*)=>its.map(price _).sum-disc}}

十四、密封类

14.1 基本介绍

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

14.2 案例演示

abstractsealedclassAmountcaseclassDollar(value:Double)extendsAmountcaseclassCurrency(value:Double,unit:String)extendsAmountcaseobjectNothingextendsAmount

当Amount没有声明为sealed时,在Temp.scala中可以定义样例类 Dollar2
当Amount声明为sealed时,在Temp.scala中不能定义样例类 Dollar2
提示错误信息: illegal inheritance from sealed class Amount

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/31 13:55:45

洛雪音乐音源终极指南:如何一键获取全网无损音乐资源

洛雪音乐音源终极指南&#xff1a;如何一键获取全网无损音乐资源 【免费下载链接】lxmusic- lxmusic(洛雪音乐)全网最新最全音源 项目地址: https://gitcode.com/gh_mirrors/lx/lxmusic- 洛雪音乐lxmusic-项目为开发者提供了完整的跨平台音乐资源获取解决方案&#xff0…

作者头像 李华
网站建设 2026/5/31 13:54:08

WebDriver Manager:Python自动化测试的驱动管理终极解决方案

WebDriver Manager&#xff1a;Python自动化测试的驱动管理终极解决方案 【免费下载链接】webdriver_manager 项目地址: https://gitcode.com/gh_mirrors/we/webdriver_manager 在Selenium自动化测试的实践中&#xff0c;浏览器驱动管理一直是开发者面临的核心痛点。每…

作者头像 李华
网站建设 2026/5/31 13:53:09

免费开源B站视频解析API:快速获取高清视频的终极解决方案

免费开源B站视频解析API&#xff1a;快速获取高清视频的终极解决方案 【免费下载链接】bilibili-parse bilibili Video API 项目地址: https://gitcode.com/gh_mirrors/bi/bilibili-parse 在当今数字内容时代&#xff0c;B站&#xff08;哔哩哔哩&#xff09;已成为中国…

作者头像 李华