一、模式匹配基本使用
object PatternVarVal {
def main(args: Array[String]): Unit = {
val a = 10
val b = 0
val op = StdIn.readLine("请输入一个运算符:")
val res = op match {
case "+" => a + b
case "-" => a - b
case "*" => a * b
case "/" if b != 0 => a / b // 可以根据需要添加守卫
case _ =>
}
println("res = " + res)
}
}
说明:
- => 后的代码可以写多行且不用 { }
- 模式匹配也是表达式,所以它也可以赋值给一个变量
- case 匹配值 之后可以添加任何布尔条件的守卫
- 模式总是从上至下匹配,当所有 case 都不匹配,会执行 case _ 分支,如果没有写 case _ 分支,会在最后抛出 MatchError
二、模式中的变量
如果在 case 关键字后跟变量名,那么 match 前的表达式会赋值给这个变量
object MatchDemo4 {
def main(args: Array[String]): Unit = {
var ch = 0
'z' match {
case 'a' => println("a...")
case 'b' => println("b...")
case 'c' => println("c...")
case ch => println("变量 ch = " + ch)
}
}
}
其实 case _ 就是 case 变量名 的特例, 你可以把`_`当做一个变量来对待,只是这个变量不再被使用了。
按照约定, Scala 期望模式变量名都以小写字母开头, 而常量名则是大写字母(只要首字母大写也可以)。
如果你使用大写字母的名称,Scala 将会在作用域范围内查找常量。
object PatternVarVal2 {
def main(args: Array[String]): Unit = {
val sample = new Sample
sample.process(10)
}
}
class Sample {
val max = 10
val Max = 10
def process(input: Int): Unit = {
// 1.指明作用域
input match {
case this.max => println(s"You matched this.max")
case _ =>
}
// 2.使用首字母大写给 scala 提示
input match {
case Max => println(s"You matched Max")
case _ =>
}
// 3.使用反引号类给 scala 提示
input match {
case `max` => println(s"You matched `max`")
case _ =>
}
// 4.直接使用max会进入模式变量,将input的值赋值给max
input match {
case max => println(s"Yout matched " + max)
case _ =>
}
}
}
--------------------输出
You matched this.max
You matched Max
You matched `max`
You matched 10
三、匹配类型
可以根据对象的类型进行匹配,避免了使用 isInstanceOf 和 asInstanceOf
object PatternType {
def main(args: Array[String]): Unit = {
val s: Any = Map("a" -> 11, 2 -> 22)
val res:String = s match {
case a: Int => println(a); "匹配到的是 Int"
case b: String => println(b); "匹配到的是 String"
case c: Array[Int] => "匹配到的是Aarray[Int]"
case d: Map[_, _] => "匹配到的是Map[String, Int]"
case _: BigInt => "匹配到的是 BigInt"
case _ => "什么都没有匹配到"
}
println(res)
}
}
----------输出
匹配到的是Map[String, Int]
说明:
- 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错
- 如果类型匹配成功之后会把 s 的值赋值给 case 后面跟着的变量, 而不需要做类型的转换
- 提供数组元素的泛型(Array[Int])是必要的,因为在创建数组时 <font color="fbbc05">数组的类型是确定的</font>,例如:Int[ ]、String[ ]
- 对于 集合类型比如 Map,提供泛型时无用的,因为 Java 的“泛型擦除”,Map[Int, Int] 和 Map[Int, String] 在匹配的时候没有区别,所以应该使用通用的泛型:Map[_, _]
四、匹配数组内容
匹配数组内容,可以在模式中使用 Array 表达式
object PatternArray {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(1, 20, 11, 1)
val res = arr match {
// 匹配只有2个元素,且元素值为(1, 2)的数组
case Array(1, 2) => "Array(1, 2)"
// 匹配长度为3的数组,并把元素一次赋值给a,b,c
case Array(a, b, c) => s"$a, $b, $c"
// case Array(1, _*) => "Array(1,...)" // 匹配任何以1开头的数组
// 匹配以1开头的数组,并把之后的所有元素放入b中,b是一个集和
case Array(1, b@_*) => b ;
// 匹配长度为3的数组,第一个是1,最后一个也是1
case Array(1, _, 1) => "1, _, 1"
// 匹配任意数组
case Array(_*) => "匹配任意数组"
case _ => "什么也没匹配到"
}
println(res)
}
}
-----------输出
Vector(20, 11, 1)
五、匹配列表内容
匹配类别内容,可以在模式中使用 List 表达式,同时可以使用 List 专用符号 ::
object MatchList {
def main(args: Array[String]): Unit = {
val arr: List[Int] = List(1, 2, 3, 5, 6)
val res = arr match {
//case List(1, 2) => "List(1, 2)"
//case List(1, _*) => "匹配以 1 开头的列表"
//case 1 :: 2 :: 3 :: Nil => "匹配List(1,2,3)"
case 1 :: abc => println(abc); "匹配以 1 开头的列表"
case _ => "啥也可没有匹配到"
}
println(res)
}
}
六、匹配元组内容
case 后可以直接使用 ( )进行元组模式的匹配
object MatchTuple {
def main(args: Array[String]): Unit = {
// val tup1: (Int, Int) = (10, 20)
val tup1: (Int, Int, Int) = (10, 20, 30)
tup1 match {
// 匹配第一个是10的两个元素的元组
// case (10, _) => println("(10, _)")
// case (a, _, _) => println(a)
// case (_, _, 30) => println("_, _, 30")
case (a, b, c) => println(a + " " + b + " " + c)
case _ => println("啥都不匹配")
}
}
}
七、匹配对象(提取器)
对象匹配的规则:
case 中的对象的 unapply 方法(提取器),返回 Some 集合则为匹配成功
返回 None 集合则为匹配失败
说明:
- Some 和 None 都是 Option 的子类
- Scala 从 2.11.1 版本开始,放松了对 unapply 返回的值的类型的限制(可以不是 Option)
返回的类型只要满足两个方法:
isEmpty:Boolen 返回 false 代表匹配成功,true 匹配失败
get:T 若匹配成功则获取到具体匹配到的值 - 其实 Some 和 None 类中均包含 isEmpty 和 get 方法
- 但是大部分情况下,还是会把要提取的数据封装到 Some 中返回
对象匹配提取单个对象
object PatternObj1 {
def main(args: Array[String]): Unit = {
val num: Double = 9
// 1.进入匹配
num match {
case My(a) => println(a) // 2. num 传入 object My 6. 匹配成功,r传回来,r=3.0
case _ => println("没有匹配到任何东西")
}
}
}
object My {
def unapply(arg: Double): My = new My(arg) // 3.以 num 为参数,创建 My 对象
}
class My(r: Double){
def isEmpty = r < 0 // 4. num 传入到 r ,9 > 0 返回 false,匹配成功
def get = math.sqrt(r) // 5.获取 sqrt(r) 的值
}
----------输出
3.0
执行过程:
- 在 case 匹配的过程中, 如果碰到这种( 伴生对象(参数) )的形式的时, 就会调用这个伴生对象的 unapply 方法, 来提前对象。
- 调 unapply 的时候, 会把前面的匹配表达式(本例中的 num )传递给 unapply
- 如果 unapply 返回的是 Some(My 具有 Some 特性), 则表示匹配成功. 并 unapply 的返回值赋值给伴生对象 —— 本例中的 My(a) , a 其实就是提取到的对象。
对象匹配提取序列
unappply 只能提取单个对象,如果想提取多个对象(序列),需要 unapplySeq 方法
object PatternObj3 {
def main(args: Array[String]): Unit = {
val names= "lisi,za,ww,zhiling"
names match {
case Names(one, two, _*) => println(one, two)
}
}
}
object Names {
def unapplySeq(s: String) = {
if (s.length == 0) None
else Some(s.split(","))
}
}
-----------输出
(lisi,za)
执行过程说明:
- case Names(one, two, three) , 这里有 3 个需要提取的结果, 所以会调用 伴生对象.unapplySeq 方法。
- 如果这个方法返回的是 Some , 并且 Some 中的序列(集合)有 3 个值,则匹配成功. 否则就匹配失败。
八、模式匹配的应用场景
变量声明的模式: 在声明变量的时候,也可已使用模式匹配(在很多语言中叫做结构)
object VarMatch {
def main(args: Array[String]): Unit = {
var (a: Int, b: Int) = (10, 20)
println(s"a = $a, b=$b")
val (aa, bb) = BigInt(10) /% 3
println(s"aa = $aa, bb = $bb")
val arr = Array(100, 200, 300, 400)
val Array(c, _, d, _*) = arr
println(s"c = $c, d = $d")
}
}
在 for 循环中的模式: 在 map 中的 key-value 也可使用模式匹配
object PatterUse {
def main(args: Array[String]): Unit = {
val map = Map(1 -> 2, 2-> 3)
for ((k, v) <- map) {
println(v)
}
}
}
九、样例类
如果一个类用 case 来修饰, 这样的类就是样例类,样例类是一种特别的类, 经过优化被用于匹配模式。
object partternObj2 {
def main(args: Array[String]): Unit = {
val user = new User("lisi", 20)
user match {
case User(name, _) => println(name)
}
val User(name, _) = user
println(name)
}
}
/*object User {
def unapply(arg: User): Option[(String, Int)] = Some((arg.name, arg.age))
}*/
// 使用 case 修饰类,类称为样例类,其中的 unapply 方法会自动声明
case class User(val name: String, val age: Int)
- 样例类会生成一系列的方法,比如:apply、unapply、toString、copy、hashcode、equals 等等。
- 样例类是为模式匹配而优化的类
- 构造器中的每一个参数都成为 val ——除非它被显式地声明为 var (不建议这样做)
- 在样例类对应的伴生对象中提供 apply 方法让你不用 new 关键字就能构造出相应的对象
- 提供 unapply 方法让模式匹配可以工作
- 将自动生成 toString、equals、hashCode
和
copy 方法
十、偏函数
定义一个偏函数: 用一对大括号括起来的一系列的 case 语句,就是一个偏函数,基础是模式匹配。
object PartialFunc {
def main(args: Array[String]): Unit = {
// foo函数参数中,传入的{}内的一系列 case 语句,就是一个偏函数
val res = foo({
case ((a, b), c) => b + c
})
println(res)
}
def foo(f: ((Int, Int), Int) => Int) = {
f((1, 2), 10)
}
}