引言
模式匹配是Scala中一项非常强大的功能,它类似于Java和C中的switch - case,但功能比switch - case要强上许多。在Scala中,everything is pattern,任何东西都可以是模式。这里分享一些模式匹配的trick。
一定要看注意事项啊!!!
一、常量匹配
模式匹配使用match关键字来开启,使用case关键字来枚举情况。 Scala中的模式匹配可以有返回值,Java中Jdk 14或以上版本的增强switch也可以有返回值。
这种匹配模式与Java或者C中的switch就很像了,编译器会用变量的值和常量按顺序进行比较,符合情况执行后续代码。
object Test {
// 这里执行结果是“zero”,因为“zero”在“零”的前面
def main(args: Array[String]): Unit = {
val num = 0
println(num match {
case 0 => "zero"
case 0 => "零"
case 1 => "one"
case 2 => "two"
case 3 => "three"
})
}
}
二、变量匹配
1. 变量命名
在每一种匹配到的情况中,我们可以为匹配到的数据命名为一个变量,在后面的代码中使用它。该变量的使用范围仅局限于该case。
object Test {
def main(args: Array[String]): Unit = {
val str = "I have something."
str match {
case something => println(s"I said:\"$something\"")
}
}
}
在Scala的模式匹配中没有default关键字,默认情况使用通配符——下划线(_)来处理。在其他case都没有匹配到是匹配该case,若没有通配case,抛出异常。
object Test {
def main(args: Array[String]): Unit = {
val str = "This is a string value."
str match {
case _ => println("Cover your ass!")
}
}
}
当使用模式匹配时,建议添加通配case,不然像下面的代码:
object Test {
def main(args: Array[String]): Unit = {
val str = "I have something."
str match {
case "Fk, Python!" => println(s"Yeah, I agree.")
}
}
}
会抛出异常导致,crush!!!
三、元组匹配
前两种匹配模式都适用于元组匹配。下划线(_)是通配符,表示该位置可以是任何数字。
object Test {
def main(args: Array[String]): Unit = {
val tuple = (1, 2, 3)
tuple match {
case (0, 0, 0) => println("They are all zeros!!!")
case (_, 5, _) => println("5 is in middle.")
case (num1, num2, num3) =>
if (num1 == num2 - 1 && num2 == num3 - 1) println("Ohhhh, it's a straight!")
else println("Just a tuple.")
}
}
}
元组匹配也可以进行嵌套匹配,如下。
case (_, (value, 3)) => ???
四、case class构造器匹配
对case class进行匹配。case class一般是像Person、Student、Animal这种bean类型的数据(也有人,比如我,喜欢叫pojo类型,或是domain类型等),匹配这种数据类型可以对其构造器进行匹配。
object Test {
case class GraphicsCard(clazz: String, typee: Int, price: Double)
def main(args: Array[String]): Unit = {
val gpu = GraphicsCard("RTX", 3090, 29999.01)
gpu match {
case GraphicsCard("GTX", _, _) => println("Sweet, honey!!!")
case GraphicsCard("AMD", _, _) => println("Yes.")
case GraphicsCard(clazz, typee, price) => println(s"$clazz $typee is Out of Stock!" +
s" Even if the price is $price yuan")
}
}
}
五、List匹配
当List中有很多元素时,我们并不需要匹配所有的元素时,可以使用下划线加星号(_*)的形式表示任意个元素,包括零个,但是只能用于末尾。
不光List可以这么匹配,Array等一些集合也可以这么匹配。
object Test {
def main(args: Array[String]): Unit = {
val list = List.empty[Int]
list match {
// 匹配空列表
case Nil => println("This is a EMPTY LIST.")
// 匹配以0开头的列表
case List(0, _*) => println("Start with 0.")
// 匹配只有一个元素的列表
case List(e) => println(s"I only have one element. It's $e")
// 匹配任意列表
case List(_*) => println("Just a list.")
// 默认匹配项
case _ => println("Cover your ass.")
}
}
}
六、类型标识符匹配
你可以为每个case使用一个标识符,当匹配对象类型与该case类型相同时,执行case代码。
object Test {
def main(args: Array[String]): Unit = {
val collection: Traversable[Int] = List(1, 2, 3)
collection match {
case list: List[Int] => println(s"This is a list ==> $list")
case queue: Queue[Int] => println(s"This is a queue ==> $queue")
case _ => println("Cover your ass.")
}
}
}
七、名称绑定
我们可以使用@为一些值或是一些属性绑定名称,在后面的case代码中可以使用。
object Test {
def main(args: Array[String]): Unit = {
val tuple = (1, 2, (3, 4, (5, 6)))
tuple match {
case (_, _, sub1 @ (_, _, sub2 @ (_ , _))) => println(s"Sub-tuple is $sub1 and $sub2")
case _ => println("Cover your ass!!!")
}
}
}
八、多模式匹配
当多种情况都是同一种处理方式时,可以使用多模式匹配减少代码行数。使用竖线(|)将不同的情况隔开,当满足其中一种情况时就会执行case。
object Test {
def main(args: Array[String]): Unit = {
val num = 1
num match {
case 1 | 3 | 5 | 7 | 9 => println("They are odd!")
case 0 | 2 | 4 | 6 | 8 => println("They are even!")
case _ => println("Cover your ass.")
}
}
}
九、守卫
跟for循环一样,case中也可以添加if守卫,模式匹配时必须模式对应并且满足守卫要求才能执行case。
这如果后面的代码中不需要用到变量,就可以用下划线(_)代替,不用起名字了。
object Test {
case class GraphicsCard(clazz: String, typee: Int, price: Double)
def main(args: Array[String]): Unit = {
val value: AnyVal = 1
value match {
case x: Int if x % 2 == 1 => println("It is odd!")
case x: Int if x % 2 == 0 => println("It is even!")
case _: Double => println("Who care.")
case _ => println("Cover your ass.")
}
}
}
十、一些注意事项
1. 常用模式匹配
我们在Scala的很多地方其实都能看到模式匹配的身影。比如说,偏函数,try - catch等。
常用模式匹配可以避免出现很长的if - else的情况,提高代码的整洁度和可读性,非常的优雅~
并且Scala中的模式匹配相较于Java、C的switch - case来说真的非常强大了,足以应付绝大多是情况。
2. 使用模式匹配常见变量
object Test {
def main(args: Array[String]): Unit = {
val array = Array(1, 2, 3)
val Array(a, b, _*) = array
println(s"a ==> $a\nb ==> $b")
}
}
3. 注意JVM的类型擦除
由于Scala是一门JVM语言,依赖于Java的JVM,所以会受到JVM的限制。
观察下面代码,思考会输出什么?
object Test {
def main(args: Array[String]): Unit = {
val array = List(1, 2, 3)
val res = array match {
case a: List[String] => "String"
case a: List[Int] => "Int"
}
println(res)
}
}
相信有人会说输出“Int”。但是其实输出的是“String”。
因为在Java中,创建一个泛型对象时虽然给定了泛型的类型,但是这个类型在JVM中时会被擦除。因此这个问题在Scala中也同样存在,因为Scala依赖于JVM。
上面代码其实相当于:
val res = array match {
case a: List[Any] => "String"
case a: List[Any] => "Int"
}
因此总是会得到“String”。在使用时需要特别注意。
结语
模式匹配的内容还是有点多的,要熟练掌握还是需要平常多多使用。
如果有大佬发现文章叙述不对或是有不好的地方可以指出,非常感谢。