详解 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))
        
    }
}
相关推荐
这孩子叫逆9 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
星星法术嗲人13 分钟前
【Java】—— 集合框架:Collections工具类的使用
java·开发语言
黑不溜秋的27 分钟前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光32 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
天上掉下来个程小白34 分钟前
Stream流的中间方法
java·开发语言·windows
xujinwei_gingko1 小时前
JAVA基础面试题汇总(持续更新)
java·开发语言
sp_wxf1 小时前
Lambda表达式
开发语言·python
Fairy_sevenseven1 小时前
【二十八】【QT开发应用】模拟WPS Tab
开发语言·qt·wps
蜡笔小新星1 小时前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
凯子坚持 c1 小时前
C语言复习概要(三)
c语言·开发语言