一、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的细节和注意事项
- 如果所有case都不匹配,那么会执行
case _分支,类似于Java中default语句 - 如果所有case都不匹配,又没有写
case _分支,那么会抛出MatchError - 每个case中,不用break语句,自动中断case
- 可以在match中使用其它类型,而不仅仅是字符
=>等价于 java switch 的:=>后面的代码块到下一个 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 类型匹配注意事项
Map[String, Int]和Map[Int, String]是两种不同的类型,其它类推。- 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错。
case i : Int => i表示将i = obj(其它类推),然后再判断类型- 如果
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 基本介绍
Array(0)匹配只有一个元素且为0的数组。Array(x,y)匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z)匹配数组有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 基本介绍
对象匹配,什么才算是匹配呢?规则如下:
- case中对象的
unapply方法(对象提取器)返回Some集合则为匹配成功 - 返回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的小结
- 构建对象时apply会被调用,比如
val n1 = Square(5) - 当将
Square(n)写在 case 后时[case Square(n) => xxx],会默认调用unapply方法(对象提取器) - number 会被传递给
def unapply(z: Double)的 z 形参 - 如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
- case中对象的unapply方法(提取器)返回some集合则为匹配成功
- 返回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的小结
- 当case后面的对象提取器方法的参数为多个,则会默认调用
def unapplySeq()方法 - 如果unapplySeq返回是Some,获取其中的值,判断得到的sequence中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first,second和third
- 其它的规则不变。
九、变量声明中的模式
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 基本介绍
- 样例类仍然是类
- 样例类用case关键字进行声明。
- 样例类是为模式匹配而优化的类
- 构造器中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做)
- 在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象
- 提供unapply方法让模式匹配可以工作
- 将自动生成toString、equals、hashCode和copy方法(有点类似模板类,直接给生成,供程序员使用)
- 除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们
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设计相关的样例类,完成商品捆绑打折出售。
要求:
- 商品捆绑可以是单个商品,也可以是多个商品。
- 打折时按照折扣x元进行设计。
- 能够统计出所有捆绑商品打折后的最终价格
13.3 创建样例类
abstractclassItem// 项caseclassBook(description:String,price:Double)extendsItem//Bundle 捆, discount: Double 折扣, item: Item*,caseclassBundle(description:String,discount:Double,item:Item*)extendsItem13.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 基本介绍
- 如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为
sealed,这个超类称之为密封类。 - 密封就是不能在其他文件中定义子类。
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