详解 Scala 的模式匹配

一、基本语法

Scala 中的模式匹配类似于 Java 中的 switch 语法,但是功能更强大

scala 复制代码
/**
	[var a = / def func() = ] var_name match {
		case value1 => statement1
		case value2 => statement2
		case value3 => statement3
		case _ => defaultOP
	}
*/
object TestMatchCase {
    def main(args: Array[String]): Unit = {
        val a = 10
        val b = 20
        
        def matchCalculate(op: Char): Any = op match {
            case '+' => a + b
            case '-' => a - b
            case '*' => a * b
            case '/' => a / b
            case _ => "非法运算符"
        }
        
        println(matchCalculate('+'))
        println(matchCalculate('*'))
        println(matchCalculate('@'))
        
    }
}
  • 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError
  • 每个 case 中,不需要使用 break 语句,会自动中断 case
  • match case 语句可以匹配任何类型,而不只是字面量
  • => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用 {} 括起来,也可以省略

二、模式守卫

在模式匹配中增加条件守卫可以匹配某个范围的数据

scala 复制代码
object TestMatchGuard {
    def main(args: Array[String]): Unit = {
        // 使用模式守卫实现绝对值
        def abs(num: Int): Int = {
            num match {
                case i: Int if i >= 0 => i
                case i: Int if i < 0 => -i
            }
        }
        
        println(abs(22))
        println(abs(0))
        println(abs(-18))
        
    }
}

三、模式匹配类型

1. 匹配常量

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

scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        def describe(x: Any): String = x match {
            case 1 => "Int one"
            case "hello" => "String hello"
            case true => "Boolean true"
            case '+' => "Char +"
            case _ => ""  // _ 是占位符,可以用其他字符替代
        }
        
        println(describe(1))
        println(describe("hello"))
        println(describe(0.3))
        
    }
}

2. 匹配类型

模式匹配可以实现类似 isInstanceOf[T] 和 asInstanceOf[T] 的功能

scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        def describeType(x: Any): String = x match {
            case i: Int => "Int " + i
            case s: String => "String " + s
            case list: List[String] => "List " + list
            case array: Array[Int] => "Array[Int] " + array.mkString(",")
            case a => "other type " + a  // 用 _ 占位不能获取到传入的变量,可以用其他字符替代
        }
        
        println(describeType(1))
        println(describeType("hello"))
        println(describeType(List("hello", "scala")))
        println(describeType(List(2, 3))) // 可以匹配,scala 会做泛型擦除
        println(describeType(Array("hello", "scala"))) // 不能匹配,Array 没有泛型擦除
        println(describeType(Array(2, 3)))
        
    }
}

3. 匹配数组

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

scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        val arrList: List[Array[Any]] = List(
            Array(0),
            Array(1, 0),
            Array(0, 1, 0),
            Array(1, 1, 0),
            Array(2, 3, 7 ,15),
            Array("hello", 20, 30)
        )
        
        for(arr <- arrList) {
            var result = arr match {
                case Array(0) => "只有一个元素为 0 的数组"
                case Array(x, y) => "有两个元素的数组:" + x + ", " + y
                case Array(0, _*) => "第一个元素为 0 的数组"
                case Array(x, 1, y) => "中间元素为 1 的三元素数组"
                case _ => "other type"
            }
            
            println(result)
        }
        
    }
}

4. 匹配列表

4.1 方式一
scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        val lists: List[List[Any]] = List(
            List(0),
            List(1, 0),
            List(0, 0, 0),
            List(1, 1, 0),
            List(1, 0, 0),
            List(88),
            List("hello")
        )
        
        for(list <- lists) {
            var result = list match {
                case List(0) => "只有一个元素为 0 的列表"
                case List(x, y) => "有两个元素的列表:" + x + ", " + y
                case List(0, _*) => "第一个元素为 0 的列表"
                case List(x, 1, y) => "中间元素为 1 的三元素列表"
                case List(a) => "只有一个元素的列表:" + a
                case _ => "other type"
            }
            
            println(result)
        }
        
    }
}
4.2 方式二
scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        val list1 = List(1, 2, 3, 4, 5)
        val list2 = List(5)
        
        // first:1 second:2 rest:List(3, 4, 5)
        list1 match {
            case first :: second :: rest => println(s"first:$first second:$second rest:$rest") // 匹配有第一个和第二个元素的列表
            case _ => println("other type")
        }
        
        // other type
        list2 match {
            case first :: second :: rest => println(s"first:$first second:$second rest:$rest")
            case _ => println("other type")
        }
        
    }
}

5. 匹配元组

scala 复制代码
object TestMatchTypes {
    def main(args: Array[String]): Unit = {
        val tupleList = List(
        	(0),
            (0, 1),
            (1, 1),
            (0, 0, 1),
            (1, 1, 0),
            ("hello", true, 0.5)
        )
        
        for(tuple <- tupleList) {
            val result = tuple match {
                case (0) => "只有一个元素 0 的元组"
                case (0, _) => "第一个元素为 0 的二元组"
                case (x, y) => "二元组:" + x + ", " + y
                case (a, 1, _) => "中间元素为 1 的三元组:" + a
                case (a, b, c) => s"三元组:$a $b $c"
                case _ => "other type"
            }
            
            println(result)
        }
        
    }
}

6. 匹配对象及样例类

6.1 自定义实现
scala 复制代码
// 定义一个类
class Student(val name: String, val age: Int)

// 定义伴生对象
object Student {
    // 实现 apply 方法用于创建对象
    def apply(name: String, age: Int): Student = new Student(name, age)
    
    // 实现 unapply 方法用于拆解对象属性
    def unapply(student: Student): Option[(String, Int)] = { // Option 防止空指针
        if(student == null) {
            None
        } else {
            Some((student.name, student.age))
        }
    }
}

object TestMatchObject {
    def main(args: Array[String]): Unit = {
        // 创建对象
        val student = new Student("tom", 18)
        
        // 对象模式匹配
        val result = student match { // 对象匹配的是各属性是否相同而不是地址
            // 不能使用 new 创建,且类的伴生对象必须实现 apply 和 unapply 方法
            case Student("tom", 18) => "tom 18" 
            case _ => "other"
        }
        
        println(result)
    }
}
  • Student("tom", 18) 在执行时,实际调用的是伴生对象中的 apply 方法构造出相应的对象
  • Student("tom", 18) 写在 case 后时会默认调用伴生对象中的 unapply 方法(对象提取器),该对象作为 unapply 方法的参数,在 unapply 方法中将该对象的 name 和 age 属性提取出来,与 new Student("tom", 18) 中的属性值进行匹配
  • 只有当 case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功;若属性不一致,或返回 None,则匹配失败
  • 若只提取对象的一个属性,则提取器为 unapply(obj: ObjClass): Option[T];若提取对象的多个属性,则提取器为 unapply(obj: ObjClass): Option[(T1,T2,T3...)];若提取对象的可变个属性,则提取器为 unapplySeq(obj: ObjClass): Option[Seq[T]]
6.2 样例类实现
  • 样例类简介

    scala 复制代码
    /**
    	定义语法:case class className(field1: type1, field2: type2,...)
    	特点:
    		1.样例类仍然是类,和普通类相比,只是自动生成了伴生对象且伴生对象中自动提供了一些常用的方法,如 apply、 unapply、 toString、 equals、 hashCode 和 copy
    		2.样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
    		3.构造器中的每一个参数默认都为 val,除非它被显式地声明为 var(不建议这样做)
    */
    case class User(name: String, age: Int) // 无需指定 val
  • 实现对象模式匹配

    scala 复制代码
    // 定义样例类
    case class User(name: String, age: Int)
    
    object TestMatchObject {
        def main(args: Array[String]): Unit = {
            val user = User("bob", 20)
            
            val result = user match {
                case User("bob", 20) => "bob 20"
                case _ => "other"
            }
            
            println(result)
            
        }
    }

四、变量声明模式匹配

scala 复制代码
object TestMatchVariable {
    def main(args: Array[String]): Unit = {
        // 使用模式匹配元组变量赋值
        val (x, y, z) = (10, "hello", true)
        println(s"x:$x y:$y z:$z")
        
        // 使用模式匹配列表变量赋值
        val List(first, second, _*) = List(2, 4, 7, 9)
        println(s"first:$first second:$second")
        
        val fir :: sec :: rest = List(2, 4, 7, 9)
        println(s"fir:$fir sec:$sec rest:$rest") // fir:2 sec:4 rest:List(7, 9)
        
        // 使用模式匹配对象属性赋值
        val Person(name, age) = Person("zhangsan", 16)
		println(s"name=$name age=$age")
    }
}

五、for循环模式匹配

scala 复制代码
object TestMatchFor {
    def main(args: Array[String]): Unit = {
        val list: List[(String, Int)] = List(("a", 1), ("b", 4), ("c",3), ("a",4))
        
        // 1. for循环直接赋值 KV
        for((k, v) <- list) { // (elem <- list)
            println(k + ": " + v) // println(elem._1 + ": " + elem._2)
        }
        
        // 2. for循环只遍历 key 或 value
        for((k, _) <- list) { // ((_, v) <- list)
            println(k) // println(v)
        }
        
        // 3. for循环遍历指定 key 或 value
        for(("a", v) <- list) { // ((k, 4) <- list)
            println("a: " + v) // println(k + ": 4")
        }
        
    }
}

六、偏函数模式匹配

偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查

1. 偏函数定义

scala 复制代码
/**
	val funcName: PartialFunction[inParamClass, outParamClass] = {
		case value => statement
	}
*/
// 定义一个获取列表第二个元素的偏函数
val getSecond: PartialFunction[List[Int], Option[Int]] = {
    case first :: second :: _ => Some(second)
}

2. 偏函数原理

scala 复制代码
// 1. 定义的偏函数会被解析翻译
val getSecond: PartialFunction[List[Int], Option[Int]] = {
    case first :: second :: _ => Some(second)
}
// =>
val getSecond = new PartialFunction[List[Int], Option[Int]] {
    //检查输入参数是否合格
    override def isDefinedAt(list: List[Int]): Boolean = list match {
        case first :: second :: _ => true
        case _ => false
    }
    
    //执行函数逻辑
    override def apply(list: List[Int]): Option[Int] = list match {
    	case first :: second :: _ => Some(second)
    }
}

// 2. 偏函数调用时采用 applyOrElse 方法,其逻辑为 if(ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法为参数不满足要求的处理逻辑
getSecond.applyOrElse(List(1,2,3), (_: List[Int]) => None)

3. 应用案例

scala 复制代码
object TestPartialFunction {
    def main(args: Array[String]): Unit = {
        // 使用偏函数实现绝对值方法
        val positiveAbs: PartialFunction[Int, Int] = {
            case x if x > 0 => x
        }
        
        val negativeAbs: PartialFunction[Int, Int] = {
            case x if x < 0 => -x
        }
        
        val zeroAbs: PartialFunction[Int, Int] = {
            case 0 => 0
        }
        
        def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs)(x)
        
        println(abs(-22))
        println(abs(18))
        println(abs(0))
        
    }
}
相关推荐
holywangle1 分钟前
解决Flink读取kafka主题数据无报错无数据打印的重大发现(问题已解决)
大数据·flink·kafka
隔着天花板看星星2 分钟前
Kafka-副本分配策略
大数据·分布式·中间件·kafka
喵叔哟9 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生15 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
Lorin 洛林22 分钟前
Hadoop 系列 MapReduce:Map、Shuffle、Reduce
大数据·hadoop·mapreduce
hopetomorrow29 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
DolphinScheduler社区37 分钟前
大数据调度组件之Apache DolphinScheduler
大数据
SelectDB技术团队38 分钟前
兼顾高性能与低成本,浅析 Apache Doris 异步物化视图原理及典型场景
大数据·数据库·数据仓库·数据分析·doris
郑祎亦39 分钟前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
小牛itbull39 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress