十二、模式匹配
12.1 match
12.1.1 基本介绍
Scala
中的模式匹配类似于
Java
中的
switch
语法,但是更加强大。
模式匹配语法中,采用
match
关键字声明,每个分支采用
case
关键字进行声明,当需要匹配时,
会从第一个
case
分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case
都不匹配,那么会执行
case _
分支,类似于
Java
中
default
语句。
12.1.2 scala 的 match 的快速入门案例
object MatchDemo01 {
def main(args: Array[String]): Unit = {
val oper = "#"
val n1=20
val n2=10
var res=0
//说明
//1. match (类似 java switch) 和 case 是关键字
//2. 如果匹配成功, 则 执行 => 后面的代码块. //3. 匹配的顺序是从上到下,匹配到一个就执行对应的 代码
//4. => 后面的代码块 不要写 break ,会自动的退出 match
//5. 如果一个都没有匹配到,则执行 case _ 后面的代码块
oper match{
case "+" =>{
res=n1+n2
println("ok")
println("hello world")
}
case "-" =>res=n1-n2
case "*" =>res=n1*n2
case "/" =>res=n1/n2
case _ => println("oper error")
}
}
12.1.3 match 的细节和注意事项
1)
如果所有
case
都不匹配,那么会执行
case _
分支,类似于
Java
中
default
语句
2)
如果所有
case
都不匹配,又没有写
case _
分支,那么会抛出
MatchError
3)
每个
case
中,不用
break
语句,自动中断
case
4)
可以在
match
中使用其它类型,而不仅仅是字符
5) =>
等价于
java swtich
的
:
6) =>
后面的代码块到下一个
case
, 是作为一个整体执行,可以使用
{}
扩起来,也可以不扩。
12.2 守卫
12.2.1 基本介绍
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫
12.2.2 应用案例
object MatchIfDemo01 {
def main(args: Array[String]): Unit = {
for (ch <- "+-*/3!"){
var sign=0
var digit=0
ch match{
// 说明..
// 如果 case 后有 条件守卫即 if ,那么这时的 _ 不是表示默认匹配
// 表示忽略 传入 的 ch
case '+' => sign= 1
case '-' => sign= -1
case _ if ch.toString.equals("3") => digit=3
case _ if ch>10 =>println("ch>10")
case _ => sign=2
}
println(ch+" "+sign+" "+digit)
// + 1 0
// - -1 0
// ch>10
// * 0 0
// ch>10
// / 0 0
// 3 0 3
// ch>10
// ! 0 0
}
}
}
12.3 模式中的变量
12.3.1 基本介绍
如果在
case
关键字后跟变量名,那么
match
前表达式的值会赋给那个变量
12.3.2 应用案例
object MatchVar {
def main(args: Array[String]): Unit = {
val ch= 'V'
ch match{
case '+' =>println("ok")
// 下面 case mychar 含义是 mychar = ch
case mychar =>println("ok~"+mychar) //ok~V
case _ => println("ok~~")
}
val ch1 = '#'
//match 是一个表达式,因此可以有返回值
//返回值就是匹配到的代码块的最后一句话的值
val res = ch1 match {
case '#' => ch1 + " hello "
case _ => println ("ok~~")
}
println("res= " + res) //res= # hello
}
}
12.4 类型匹配
12.4.1 基本介绍
可以匹配
对象的任意类型
,这样做避免了使用
isInstanceOf
和
asInstanceOf
方法
12.4.2 应用案例
object MatchTypeDemo01 {
def main(args: Array[String]): Unit = {
val a=5
//说明 obj 实例的类型 根据 a 的值来返回
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")
//说明
//1. 根据 obj 的类型来匹配
// 返回值
val result =obj match{
//a: 相当于把obj赋值给a
case a: Int=>a
case b: Map[String, Int] => "对象是一个字符串-数字的 Map 集合"
case c: Map[Int, String] => "对象是一个数字-字符串的 Map 集合"
case d: Array[String] => d //"对象是一个字符串数组"
case e: Array[Int] => "对象是一个数字数组"
case f: BigInt => Int.MaxValue
case _ => "啥也不是"
}
println(result)
}
}
12.4.3 类型匹配注意事项
1) Map[String, Int]
和
Map[Int, String]
是两种不同的类型,其它类推。
2)
在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错
.

3)
一个说明
:
val result = obj match {
case i : Int => i
} case i : Int => i
表示 将
i = obj (
其它类推
)
,然后再判断类型
4)
如果
case _
出现在
match
中间,则表示隐藏变量名,即不使用
,
而不是表示默认匹配
12.5 匹配数组
12.5.1 基本介绍
1) Array(0)
匹配只有一个元素且为
0
的数组。
2) Array(x,y)
匹配数组有两个元素,并将两个元素赋值为
x
和
y
。当然可以依次类推
Array(x,y,z)
匹配数组有 3
个元素的等等
....
3) Array(0,_*)
匹配数组以
0
开始
12.5.2 应用案例
object MatchArr {
def main(args: Array[String]): Unit = {
val arrs = Array(Array(0), Array(1, 0), Array(0, 1, 0),
Array(1, 1, 0), Array(1, 1, 0, 1))
for (arr <- arrs) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "和" + y
case Array(0, _*) => "以0开头的数组"
case _ => "什么集合都不是"
}
println("result= " + result)
// result= 0
// result= 1和0
// result= 以0开头的数组
// result= 什么集合都不是
// result= 什么集合都不是
}
println("===============================")
//给你一个数组集合,如果该数组是 Array(10,20) , 请使用默认匹配,返回 Array(20,10)
val arrs2 = Array(Array(0), Array(1, 0), Array(0, 1, 0),
Array(1, 1, 0), Array(1, 1, 0, 1))
for (arr2 <- arrs2) {
val result2 = arr2 match {
case Array(x, y) => Array(y, x)
case _ => "不处理了"
}
println("数组的前后交换:" + result2)
}
}
}
12.6 匹配列表
应用案例
object MatchList {
def main(args: Array[String]): Unit = {
for(list <- Array(List(0),List(1,0),List(88),List(0,0,0),List(1,0,0))){
val result =list match{
case 0 :: Nil => "0" //
case x :: y :: Nil => x + " " + y //
case 0 :: tail => "0 ..." //
case x :: Nil => x
case _ => "something else"
}
println(result)
//1. 0
//2. 1 0
//88
//3. 0 ...
// 4. something else
}
}
}
12.7
匹配元组
应用案例
object MatchTupleDemo01 {
def main(args: Array[String]): Unit = {
//如果要匹配 (10, 30) 这样任意两个元素的对偶元组,应该如何写
for (pair <- Array((0, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
val result = pair match { //
case (0, _) => "0 ..." //
case (y, 0) => y //
case (x, y) => (y, x) //"匹配到(x,y)" + x + " " + y
case _ => "other" //.
}
//1. 0 ...
//2. 1
//3.(30,10)
//(1,1)
//4. other
println(result)
}
}
}
12.8 对象匹配
12.8.1 基本介绍
对象匹配,什么才算是匹配呢?,规则如下
:
1) case
中对象的
unapply
方法
(
对象提取器
)
返回
Some
集合则为匹配成功
2)
返回
None
集合则为匹配失败
12.8.2 快速入门案例
object MatchObject {
def main(args: Array[String]): Unit = {
// 模式匹配使用:
val number:Double =36.0
number match{
//说明 case Square(n) 的运行的机制
//1. 当匹配到 case Square(n)
//2. 调用 Square 的 unapply(z: Double),z 的值就是 number
//3. 如果对象提取器 unapply(z: Double) 返回的是 Some(6) ,则表示匹配成功,同时
// 将 6 赋给 Square(n) 的 n
//4. 如果对象提取器 unapply(z: Double) 返回的是 None ,则表示匹配不成功
case Square(n) => println("匹配成功 n=" + n)
case _ => println("nothing matched")
}
}
}
object Square{
//说明
//1. unapply 方法是对象提取器
//2. 接收 z:Double 类型
//3. 返回类型是 Option[Double]
//4. 返回的值是 Some(math.sqrt(z)) 返回 z 的开平方的值,并放入到 Some(x)
def unapply(z:Double):Option[Double] =Some(math.sqrt(z))
def apply(z:Double):Double=z*z
}
12.8.3 应用案例 2
object MatchObjectDemo2 {
def main(args: Array[String]): Unit = {
val namesString = "Alice,Bob,Thomas" //字符串
//说明
namesString match {
// 当 执行 case Names(first, second, third)
// 1. 会调用 unapplySeq(str),把 "Alice,Bob,Thomas" 传入给 str
// 2. 如果 返回的是 Some("Alice","Bob","Thomas"),分别给 (first, second, third)
// 注意,这里的返回的值的个数需要和 (first, second, third)要一样
// 3. 如果返回的 None ,表示匹配失败
case Names(first, second, third) => {
println("the string contains three people's names")
// 打印字符串
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
}
}
//object
object Names {
//当构造器是多个参数时,就会触发这个对象提取器
def unapplySeq(str: String): Option[Seq[String]] = {
//使用str.contains(",")判断字符串str是否包含逗号。如果包含逗号,则使用str.split(",")将字符串按逗号拆分成一个字符串数组
if (str.contains(",")) Some(str.split(","))
else None
}
}
代码的小结
1)
当
case
后面的对象提取器方法的参数为多个,则会默认调用
def unapplySeq()
方法
2)
如果
unapplySeq
返回是
Some
,获取其中的值
,
判断得到的
sequence
中的元素的个数是否是三个如果是三个,则把三个元素分别取出,赋值给 first
,
second
和
third
3)
其它的规则不变
.
12.9 变量声明中的模式
12.9.1 基本介绍
match
中每一个
case
都可以单独提取出来,意思是一样的
.
12.9.2 应用案例
object MatchVarDemo {
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(first,second)
}
}
12.10 for 表达式中的模式
12.10.1 基本介绍
for
循环也可以进行模式匹配
.
12.10.2 应用案例
object MatchForDemo {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for((k,v) <- map){
println(k+" -> " +v)
// A -> 1
// B -> 0
// C -> 3
}
for((k,0) <- map){
println(k+" -->"+0) //B -->0
}
for((k,v) <- map if v==0) {
println(k+" --->" +v) //B --->0
}
}
}
12.11 样例(模板)类
12.11.1 样例类快速入门
object CaseClassDemo01 {
def main(args: Array[String]): Unit = {
println("hello")
}
}
abstract class Amount
case class Dollar(value:Double) extends Amount //样例类
case class Currency(value:Double,unit:String) extends Amount //样例类
case object NoAmount extends Amount //样例类
//类型(对象) =序列化(serializable)==>字符串(1.你可以保存到文件中【freeze】2.反序列化,2 网络传输)
12.11.2 基本介绍
1)
样例类仍然是类
2)
样例类用
case
关键字进行声明。
3)
样例类是为
模式匹配而优化
的类
4)
构造器中的每一个参数都成为
val
——除非它被显式地声明为
var
(不建议这样做)
5)
在样例类对应的伴生对象中提供
apply
方法让你不用
new
关键字就能构造出相应的对象
6)
提供
unapply
方法让模式匹配可以工作
7)
将自动生成
toString
、
equals
、
hashCode
和
copy
方法
(
有点类似模板类,直接给生成,供程序员使用)
8)
除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们
12.11.3 样例类最佳实践 1
object CaseClassDemo02 {
def main(args: Array[String]): Unit = {
//该案例的作用就是体验使用样例类方式进行对象匹配简洁性
for(amt <- Array(Dollar2(1000.0),Currency2(10000.0,"RMB"),NoAmount2)){
val result = amt match{
case Dollar2(v) => "$"+v
case Currency2(v,u) => v +" "+u
case NoAmount2 => ""
}
println(amt+": "+result)
// Dollar2(1000.0): $1000.0
// Currency2(10000.0,RMB): 10000.0 RMB
// NoAmount2:
}
}
}
abstract class Amount2
case class Dollar2(value:Double) extends Amount2 //样例类
case class Currency2(value:Double,unit:String) extends Amount2 //样例类
case object NoAmount2 extends Amount2 //样例类
12.11.4 样例类最佳实践 2
说明
样例类的
copy
方法和带名参数
copy
创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
代码实现
object CaseClassDemo03 {
def main(args: Array[String]): Unit = {
val amt = new Currency3(3000.0, "RMB")
val amt2 = amt.copy() //克隆,创建的对象和 amt 的属性一样
println("amt2.value="+amt2.value+" amt2.unit="+amt2.unit) //amt2.value=3000.0 amt2.unit=RMB
println(amt2) //Currency3(3000.0,RMB)
val amt3 = amt.copy(value = 8000)
println(amt3) //Currency3(8000.0,RMB)
val amt4 = amt.copy(unit = "美元")
println(amt4) //Currency3(3000.0,美元)
}
}
abstract class Amount3
case class Dollar3(value:Double) extends Amount3 //样例类
case class Currency3(value:Double,unit:String) extends Amount3 //样例类
case object NoAmount3 extends Amount3 //样例类
12.12 case 语句的中置(缀)表达式
12.12.1 基本介绍
什么是中置表达式?
1 + 2
,这就是一个中置表达式。如果
unapply
方法产出一个元组,你可以在
case
语句中使用中置表示法。比如可以匹配一个
List
序列
12.12.2 应用实例
object MidCase {
def main(args: Array[String]): Unit = {
List(1,10,80,4,5) match{
case first::second::rest =>println(first+" "+second+" "+rest.length+" "+rest)
case _ => println("匹配不到...")
//1 10 3 List(80, 4, 5)
}
}
}
12.13匹配嵌套结构
12.13.1 基本介绍
操作原理类似于正则表达式
12.13.2 最佳实践案例-商品捆绑打折出售
现在有一些商品,请使用
Scala
设计相关的样例类,完成商品捆绑打折出售。要求
1)
商品捆绑可以是单个商品,也可以是多个商品。
2)
打折时按照折扣
x
元进行设计
.
3)
能够统计出所有捆绑商品打折后的最终价格
object ScalesDemo0 {
def main(args: Array[String]): Unit = {
//这里给出了一个具体的打折的案例
//(40-10)+(80+30-20)=120
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关", 80), Book(" 《围城》", 30)))
//知识点一:使用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) //res2=(Book(漫画,40.0),ArraySeq(Bundle(文学作品,20.0,ArraySeq(Book(《阳关,80.0), Book( 《围城》,30.0)))))
//知识点 3-不使用_*绑定剩余 Item 到 rest
val res3=sale match {
case Bundle(_,_ ,art3 @Book(_,_),rest3) =>(art3,rest3)
}
println("res3="+res3) //res3=(Book(漫画,40.0),Bundle(文学作品,20.0,ArraySeq(Book(《阳关,80.0), Book( 《围城》,30.0))))
}
}
//设计样例类
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
12.13.3 最佳实践案例-商品捆绑打折出售(最终解决方案)
object ScalesDemo0 {
def main(args: Array[String]): Unit = {
//这里给出了一个具体的打折的案例
//(40-10)+(80+30-20)=120
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关", 80), Book(" 《围城》", 30)))
//知识点一:使用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) //res2=(Book(漫画,40.0),ArraySeq(Bundle(文学作品,20.0,ArraySeq(Book(《阳关,80.0), Book( 《围城》,30.0)))))
//知识点 3-不使用_*绑定剩余 Item 到 rest
val res3=sale match {
case Bundle(_,_ ,art3 @Book(_,_),rest3) =>(art3,rest3)
}
println("res3="+res3) //res3=(Book(漫画,40.0),Bundle(文学作品,20.0,ArraySeq(Book(《阳关,80.0), Book( 《围城》,30.0))))
println(price(sale)) //120.0
}
//解决方案
def price(it:Item):Double={
it match {
case Book(_,p) =>p
case Bundle(_,disc,its@_*) =>its.map(price).sum-disc
}
}
}
//设计样例类
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
解决原理:
