Skip to content

模式匹配

Scala中的模式匹配类似于 Java中的switch语法,但是scala从语法中补充了更多的功能,可以按照指定的规则对数据或对象进行匹配,所以更加强大。

1. 基本语法

模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。scala中没有default关键字。如果没有匹配成功,就会报错。这一点和Java中不一样。

scala
def main(args: Array[String]): Unit = {
    val age = 10
    age match {
        case 10 => println("结果为10")
        case 20 => println("结果为20")
        case _ => println("结果为其他")
    }
}

运行结果:
Alt text

✍️总结_(下划线)的使用场景

  1. 导包使用import
    比如 import xxx.yyy._
  2. 可以作为标识符
    比如 val _ = "张三"
  3. 可以将函数作为对象使用
    比如 val obj = fun _
  4. 如果匿名函数只使用一次,那么采用下划线代替
    比如 _+_
  5. Import类时,使用下划线代表java中的星号
    比如 import java.util._
  6. Import类时,用于屏蔽类
    比如 import java.util.{Date=>_}
  7. 对象属性初始化
    比如 var name: String = _
  8. 模式匹配时表示任意值
    比如 case _ => {}
  9. 模式匹配时泛型用下划线表示任意类型
    比如 case s: List[_] =>{}

2. 匹配常量

Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。

scala
def main(args: Array[String]): Unit = {
    println(describe(5))
    println(describe("hello"))
    println(describe(true))
    println(describe('+'))
    println(describe(false))
}

def describe(x: Any): String = {
    x match {
        case 5 => "Int five"
        case "hello" => "Starting hello"
        case true => "Boolen true"
        case '+' => "Char +"
    }
}

运行结果:
Alt text

3. 匹配类型

需要进行类型判断时,可以使用前文所学的isInstanceOf[T]asInstanceOf[T],也可使用模式匹配实现同样的功能。

scala
def main(args: Array[String]): Unit = {
    println(describe(5))
    println(describe("test"))
    // 如果所有的规则都不匹配,会查找下划线分支,
    // 如果想要使用下划线代表的值,一般会给下划线取个别名
    println(describe(false))
    println(describe(Array(1, 4, 7, 8)))
    println(describe(Array("1", "4", "7", "8")))
    println(describe(List(1,2,3,5)))
    // 类型匹配时,泛型不会被匹配,特殊的Array除外,因为Array不是真正的泛型
    // 真正的泛型只会在编译时有效,而Array的泛型是在运行时也有效,可以理解成Array[Int]它是一个整体类型
    println(describe(Set("hello", "test")))
    // 如果case直接后面写类型,并不表示类型匹配,而是对象匹配
    // 这样写会被编译器认为匹配伴生对象
    println(describe(true))
}

def describe(x: Any): String = {
    x match {
        case i: Int => "Int"
        case b: Boolean => "Boolean"
        case s: String => "String hello"
        case m: List[_] => "List"
        case c: Array[Int] => "Array[Int]"
        case d: Set[Int] => "Set[Int]"
        case something => "something else "+something
    }
}

运行结果:
Alt text

4. 匹配数组

scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。

scala
def main(args: Array[String]): Unit = {
    for (arr <- Array(
        Array(1),
        Array(1, 0),
        Array(0, 1, 1),
        Array(1,2,4,6),
        Array("hello", 1)
    )){
        val result = arr match {
            case Array(1)=> "1"
            case Array(x, y) => x+" "+y
            case Array(0, _*) => "以0开头的数组"
            case _ => "something else"
        }
        println(result)
    }
}

运行结果:
Alt text

5. 匹配列表

scala
def main(args: Array[String]): Unit = {
    // 方式一
    for (arr <- List(
        List(0),
        List(1, 0),
        List(0, 0, 0),
        List(1,0,0),
        List(88)
    )){
        val result = arr match {
            case List(0) => "0" //匹配 List(0)
            case List(x, y) => x + "," + y //匹配有两个元素的 List
            case List(0, _*) => "0 ..."
            case _ => "something else"
        }
        println(result)
    }
    println("*******************************")
    // 方式二
    val list: List[Int] = List(1, 2, 5, 6, 7)
    list match {
        // 要求列表含有三个元素 
        // 特殊的List(1,2)也满足条件,因为可以分为1::2::Nil
        case first :: second :: rest => println(first + "-" + second + "-" + rest)
        case _ => println("something else")
    }
}

运行结果:
Alt text

6. 匹配元组

scala
def main(args: Array[String]): Unit = {
    //对一个元组集合进行遍历
    for (tuple <- Array(
        (0, 1),
        (1, 0),
        (1, 1),
        (1, 0, 2),
        (1, 0, 2, 0)
    )) {
        val result = tuple match {
            case (0, _) => "0 ..." //是第一个元素是 0 的元组
            case (y, 0) => y + "0" // 匹配第二个元素是 0 的对偶元组
            case (a, b) => a + " " + b
            case _ => "something else" //默认
        }
        println(result)
    }
}

运行结果:
Alt text

7. 模式匹配的应用

7.1 变量声明中的模式匹配

Scala的模式匹配不仅仅应用在match使用,绝对不止Java中switch的常量匹配的强大。

scala
def main(args: Array[String]): Unit = {
    val user = (1, "jack", 32)
    println(user._1)
    println(user._2)
    println(user._3)
    // 模式匹配可以简化
    // 给元组元素命名
    val (id, name, age) = user
    println(s"id: $id, name: $name, age: $age")
}

运行结果:
Alt text

7.2 for表达式中的模式匹配

scala
def main(args: Array[String]): Unit = {
    val dataMap = Map("1"-> 32, "2"-> 44)
    for (elem <- dataMap) {
        if(elem._2==44){
            println(elem._1 +" : "+elem._2)
        }
    }
    // 可以简化不用序列号
    // 告诉编译器,只能匹配上value为44
    for((k, 44) <- dataMap){
        println(k+" : "+44)
    }
    println("&&&&&&&&&&&&&&&&&&&&&&&&&")
    // 统计不同省份的物品下单数量
    val list1 = List(
        (("河南", "鞋"), 4),
        (("河南", "裤子"), 1),
        (("成都", "鞋"), 3),
        (("河南", "衣服"), 2),
        (("成都", "裤子"), 3)
    )
    // 代码中序列化用的太多,不太清晰逻辑
    list1.map(
        item => {
            (item._1._1, (item._1._2, item._2))
        }
    ).foreach(println)
    println("&&&&&&&&&&&&&&&&&&&&&&&&&")
    // 使用模式匹配
    // 小括号在匿名函数中表示参数列表,所以无法直接作为模式匹配的元组规则
    // 使用case明确告诉编译器这里使用模式匹配,再将小括号变成大括号
    list1.map{
        case ((pri, goods), num) =>{
            (pri, (goods, num))
        }
    }.foreach(println)
}

运行结果:
Alt text

8. 匹配对象

scala
def main(args: Array[String]): Unit = {
    val user: User = getUserFromDB()
    // 判断得到的用户是否是我们想要的30岁用户
    user match {
        // 普通的对象无法再模式匹配中使用,因为对象的匹配其实就是属性的匹配
        // 需要通过对象获得其属性, 其方法是:unapply
        // 它会将对象的每个属性进行值比较,完全相同才成立匹配成功
        case User("jack", 30)=>{
            println("这就是我们想要的用户")
        }
        case _ =>{
            println("没找到用户")
        }
    }
}

class User {
    var name: String = _
    var age: Int = _
}
object User{
    def apply(name: String, age: Int): User= {
        val user = new User()
        user.age = age
        user.name = name
        user
    }
    // unapply方法将user对象的name和age属性提取出来,形成tuple
    def unapply(user: User): Option[(String, Int)] ={
        Option((user.name, user.age))
    }
}
def getUserFromDB(): User={
    // 实际调用的是User伴生对象中的apply方法,因此不用new就能构造出相应的对象
    User("jack", 30)
}

运行结果:
Alt text 从上面看对象匹配比较麻烦,需要额外添加特定的方法才能实现, Scala额外提供了一个简单的实现方式进行匹配。使用样例类进行实现, 在类的前面加上关键字case

9. 样例类

9.1 样例类说明

  1. 样例类的构造参数会自动作为类的属性,默认不能改变,使用val声明的,如果想要改,需要使用var。
  2. 样例类就是一个普通类,只不过可以应用在模式匹配中用于匹配对象。
  3. 样例类声明很简单,但是编译后会自动产生大量的方法,都是由编译器完成。和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。

9.2 使用样例类

用样例类会节省大量代码。

scala
def main(args: Array[String]): Unit = {
    val user: MyUser = getUserFromDB()
    // 判断得到的用户是否是我们想要的30岁用户
    user match {
        case MyUser("jack", 30) => {
            println("这就是我们想要的用户")
        }
        case _ => {
            println("没找到用户")
        }
    }
}
// 声明样例类
case class MyUser(name: String, var age: Int)

def getUserFromDB(): MyUser = {
    MyUser("jack", 30)
}

运行结果:
Alt text

10. 偏函数

偏函数也是函数的一种,和全函数对比来说,全函数就是对集合所有的数据进行处理。所谓偏函数表示对满足条件的数据进行处理。

scala
def main(args: Array[String]): Unit = {
    val list = List(1, 2, "3", 4)
    // 数字加1 ,字符串不要
    list.filter(_.isInstanceOf[Int]).map{
        case i:Int=> i+1
    }.foreach(println)
    println("*************************")
    // 使用偏函数直接实现
    // map不支持偏函数
    list.collect{
        case i:Int=> i+1
    }.foreach(println)
}

运行结果:
Alt text

如何查看那些方法支持偏函数呢?

比如上文中的collect方法,查看源码:
Alt text