模式匹配概念
对数据进行深度分解和检查的一个功能,极其强大
涉及到两个术语:
- extraction 提取
- deconstruction 解构
在后面深入分析他俩
基础语法
类比Java中的swith
选择器 match {分支们}
每个分支就是一个模式,都是以case
作为开头
我们要研究有哪些模式
模式类型
- 通配模式
- 常量模式
- 变量模式
- 构造方法模式
- 序列模式
- 元组模式
- 附带类型模式
通配模式
意思就是啥都能匹配
def myMatch(x:Any) = x match {
case _ => println("通配")
}
对于这个方法,我们传入任何数据都能匹配到_ ,_
是通配模式的符号。
myMatch(1)
myMatch("love")
这个模式选项一般放在最后,用来匹配其它选项
常量模式
所谓常量模式就是某个固定值直接匹配
def myMatch(x:Any) = x match {
case 1 => println("数字1")
case _ => println("其它")
}
变量模式
变量模式和通配有点相似,但是变量模式能完成数据的绑定,通配模式是不具有这个功能的
def myMatch(x:Any) = x match {
case 1 => println("数字1")
// v 是一个变量,并且绑定了值
case v => println(v)
}
关于常量还是变量的问题有一些隐晦的知识稍微了解下
观察下面的代码
val number = 1
def myMatch(x:Any) = x match {
case number => println("数字1")
// v 是一个变量,并且绑定了值
case v => println(v)
}
看上去number就是1,应该是常量,但是实际上,Scala处理的时候将它视为了变量模式,和下面的v是一样的。
val Number = 1
def myMatch(x:Any) = x match {
case Number => println("数字1")
// v 是一个变量,并且绑定了值
case v => println(v)
}
上面的number首字母大写,scala处理的时候会去寻找该变量的真实值,将其视为常量,所以Scala用大小写区分了常量和变量模式。
另外还有集中情况也视为常量
import Math.PI
def myMatch(x:Any) = x match {
case PI => println("圆周率")
// v 是一个变量,并且绑定了值
case v => println(v)
}
这是从其它地方导入的值,放在这里也会视为常量
比如下面的写法也视为常量
object Demo:
val girlName = "迪丽热巴"
end Demo
import Math.PI
def myMatch(x:Any) = x match {
case PI => println("圆周率")
// 常量模式
case Demo.girlName => println("漂亮不")
case v => println(v)
}
某个类的实例引用其数据,也视为常量
class Girl(val name:String):
end Girl
val g = Girl("fbb")
def myMatch(x:Any) = x match {
// 这也是常量
case g.name => println("范冰冰")
case v => println(v)
}
还有一种情况,可以把普通变量转为常量模式,借助反单引号也可以完成这个工作
val name = "fbb"
def myMatch(x:Any) = x match {
case `name` => println("范冰冰")
case v => println(v)
}
注意一点,scala区分的核心是写法,并非真实值
import Math.PI
val pi = PI
def myMatch(x:Any) = x match {
case pi => println("圆周率???")
case v => println(v)
}
这里使用了变量接收了PI,在case后使用了pi,此时其实它是变量模式
myMatch("fbb")
myMatch(Math.PI)
这里的测试都会进入pi case,因为Scala是按照形式来的,不是按照真实值来的。
小结
- 导入的大小常量
- 类级别实例持有数据
- 反单引号包裹的变量
- 大写开头的变量
上述几种情况都会视为常量模式
构造方法模式
case class Girl(val name:String,val age:Int):
def this(name:String) = this(name,18)
end Girl
def myMatch(x:Any) = x match {
case Girl(a,b) => println(s"$a,$b")
case v => println(s"other $v")
}
Girl这里就是一种构造方法模式,
模式匹配能够解构任意深度,你还可以指定构造中某个数据必须是某个值,比如
def myMatch(x:Any) = x match {
case Girl(a,18) => println(s"$a, 真18")
case Girl(a,b) => println(s"$a,$b")
case v => println(s"other $v")
}
第一个case会匹配到构造出Girl的,并且age必须为18,第二个就没有18的要求,对于更细化的case应该写在前面,否则无法匹配到。
序列模式
主要是List 这样的数据可以进行匹配
def myMatch(x:Any) = x match {
case List(1,3,_) => println("1,3开头 共三个元素的列表")
case v => println(s"other $v")
}
myMatch(List(1,3,2))
myMatch(List(1,3,4,5,6))
第一个match是三个元素而且1,3开头符合,第二个List长度不满足,所以只能匹配到v。
如果你想确定某几个元素,长度不限制需要使用_*
写法,并且要在最后
def myMatch(x:Any) = x match {
case List(1,_,3,_*) => println("1 中间随意,然后3,其余多少个元素无所谓")
case v => println(s"other $v")
}
myMatch(List(1,3,3)) // 能匹配
myMatch(List(1,2,3,5,6)) // 能匹配
myMatch(List(3,5,6))// 第一个数据就不符合,无法匹配
元组模式
针对Tuple
def myMatch(x:Any) = x match {
case (1,"love",_) => println("1111")
case v => println(s"other $v")
}
myMatch((1,"love","haha")) // 能匹配
myMatch((2,"love","haha"))
元组中不要使用_*
这样的写法,因为元组本身有长度限制和_*
就不搭。
附加类型模式
对数据用数据类型描述,也可以匹配
def myMatch(x:Any) = x match {
case a:String => println("字符串")
case v => println(s"other $v")
}
有了类型,可以做更多的事情
def myMatch(x:Any) = x match {
case a:String => println(a.length)
case v => println(s"other $v")
}
这里a明确是String,所以可以使用length,a和x虽然是一个东西,由于类型描述不一样,你无法使用x.length
,这一点要注意下
模式守卫
模式守卫就是在某个匹配模式后可以加入一些逻辑控制
def myMatch(x:Any) = x match {
case a:String if a.length > 2 => println(s"$a 够长")
case v => println(s"other $v")
}
myMatch("开心")// 长度不满足,匹配v
myMatch("开心吗") // 长度满足,匹配 a
myMatch((1,"love","haha"))
变量绑定
变量绑定是在某个模式中,将某个数据再次绑定到某个变量中,需要使用@
符号,我们看一个案例
def myMatch(x:Any) = x match {
case Girl(name, a @ age) => println(a)
case v => println(s"other $v")
}
这里的a绑定了后面的age,这个案例非常简单,以至于看不出它有啥用。
case class Girl(val name:String,val age:Int,val boyFriend: BoyFriend):
end Girl
case class BoyFriend(val name:String , val balance:Double):
end BoyFriend
def myMatch(x:Any) = x match {
case Girl(name, age, b @ BoyFriend(_,_)) => println(b.name + b.balance)
case v => println(s"other $v")
}
用这种模式不用在乎里面是什么数据,并且还能完成数据的引用,如果不用这种方式,像下面这样,我们想得到BoyFriend里面的数据怎么弄?
def myMatch(x:Any) = x match {
case Girl(name, age, BoyFriend(_,_)) => println()
case v => println(s"other $v")
}
还有一细致的点,在Scala编程中有一个案例,我们引用说明
abstract class Expr
case class Number(number: Double) extends Expr
case class Unop(oprator:String,arg:Expr) extends Expr
def myMatch(x:Any) = x match {
case Unop("abs",e @ Unop("abs",_)) => e
case _ =>
}
val r1 = myMatch(
Unop("abs",
Unop(
"abs",
Number(3.3)
)
)
)
println(r1)
打印
Unop(abs,Number(3.3))
这里的含义是,对一个数连续进行两个绝对值,这样写之后可以简化为只求一次绝对值
Unop("abs",
Unop(
"abs",
Number(3.3)
)
)
本身可以简化为 Unop(“abs”,_) 而_
部分又可以视为Unop("abs",_)
,这部分本身赋值给变量e了,所以后续的操作直接使用e本身做abs操作即可,不用走两次。
这段逻辑有点绕,需要一定的理解能力。
我们写一段伪代码来描述其逻辑,如果不是模式匹配,原始代码
val x = Unop("abs",
Unop(
"abs",
Number(3.3)
)
)
进行求值的之后要
(x.arg.arg x.arg.operator) x.operator
经过模式匹配后由于直接得到了e,就是一个
Unop("abs", _)
所以如果利用它计算的话,就是属于
x.arg x.operator
我这里提供一个改写的可实验论证的代码,证明这一点
object Main {
def main(args: Array[String]): Unit = {
val x = Unop("abs",
Unop(
"abs",
Number(3.3)
)
)
// 这里要计算两次
val v = x.cal();
println(v)
println("==============")
val x2 = myMatch(x)
val x3 = x2.asInstanceOf[Unop]
// 这里只需要计算一次
val v2 = x3.cal()
println(v2)
}
def myMatch(x: Any):Any = x match {
case Unop("abs", e @ Unop("abs", _)) => e
case _ => None
}
}
abstract class Expr:
def cal():Any
end Expr
case class Number(number: Double) extends Expr:
override def cal(): Any = number
end Number
case class Unop(oprator: String, arg: Expr) extends Expr:
override def cal(): Any = oprator match {
case "abs" => {
println("计算绝对值")
arg.cal()
}
case _ => None
}
end Unop
元组增强功能
scala3对元组进行了增强,使其更像列表,但是保留了每个元素的类型,*:
是一个符号,用于连接数据
val heros = Seq(
("傲之追猎者","雷恩加尔","狮子狗"),
("傲之追猎者","雷恩加尔","狮子狗","今晚猎个痛快"),
("海洋之灾","普朗克","船长"),
("武器大师","贾科斯","一灯"),
)
val result = heros.map{
case "傲之追猎者" *: second *: third *:EmptyTuple => s"$third"
case first *: other => s"$first : $other"
}
println(result)
结果
List(狮子狗, 傲之追猎者 : (雷恩加尔,狮子狗,今晚猎个痛快), 海洋之灾 : (普朗克,船长), 武器大师 : (贾科斯,一灯))
其中的EmptyTuple描述后面是空,所以第一个case就是三个元素限定死。