模式匹配-基础使用

58 阅读3分钟

Scala 的模式匹配是一种强大的多分支条件判断机制,比 Java 的 switch 更灵活、更具表达力,可匹配值、类型、结构、样例类等,甚至支持守卫条件和偏函数。本文从基础到进阶全面讲解 Scala 模式匹配的核心用法。

一、基础语法

模式匹配通过 match 关键字实现,语法结构:

scala

变量/表达式 match {
  case 模式1 => 逻辑1  // 匹配成功执行对应逻辑
  case 模式2 => 逻辑2
  case _ => 默认逻辑   // 通配符,匹配所有未命中的情况(类似 switch 的 default)
}

核心特点

  • 匹配是顺序执行的,命中第一个匹配的 case 后立即返回,不会穿透(无需 break);
  • 若未匹配到任何 case 且无 _,会抛出 MatchError
  • 匹配结果可直接赋值给变量(表达式特性)。

二、常见匹配场景

1. 值匹配(基础)

匹配固定值(数字、字符串、布尔值等),类似增强版 switch

scala

def matchValue(x: Any): String = x match {
  case 1 => "数字 1"
  case "scala" => "字符串 scala"
  case true => "布尔值 true"
  case _ => "未匹配到"  // 通配符,兜底
}

println(matchValue(1))       // 输出:数字 1
println(matchValue("java"))  // 输出:未匹配到

2. 类型匹配

匹配变量的实际类型(而非声明类型),语法:case 变量: 类型

scala

def matchType(x: Any): String = x match {
  case s: String => s"字符串:$s"
  case i: Int => s"整数:$i"
  case l: List[_] => s"列表:$l"  // 通配符 _ 表示任意泛型的 List
  case _: Double => "浮点数(忽略值)"
  case _ => "未知类型"
}

println(matchType(List(1,2,3)))  // 输出:列表:List(1, 2, 3)
println(matchType(3.14))         // 输出:浮点数(忽略值)

⚠️ 注意:泛型擦除问题 ——Scala 在运行时会擦除泛型类型,因此 case l: List[Int] 无法精准匹配 List[Int],需结合其他方式(如守卫)。

3. 守卫条件(Guard)

在 case 后加 if 条件,实现更精细的匹配:

scala

def matchGuard(x: Int): String = x match {
  case i if i > 0 => s"正数:$i"
  case i if i < 0 => s"负数:$i"
  case 0 => "零"
  case _ => "非整数"
}

println(matchGuard(10))  // 输出:正数:10
println(matchGuard(-5))  // 输出:负数:-5

4. 集合匹配(List/Array/Tuple)

匹配集合的结构(长度、元素值、通配符):

(1)List 匹配

scala

def matchList(list: List[Int]): String = list match {
  case Nil => "空列表"
  case List(1, 2, 3) => "固定元素列表 [1,2,3]"
  case 1 :: Nil => "单元素列表 [1]"  // :: 是列表构造符
  case head :: tail => s"非空列表,头:$head,尾:$tail"  // 匹配任意非空列表
  case _ => "其他列表"
}

println(matchList(List(1,2,3)))  // 输出:固定元素列表 [1,2,3]
println(matchList(List(5,6,7))) // 输出:非空列表,头:5,尾:List(6, 7)
(2)Tuple 匹配

scala

def matchTuple(tuple: Any): String = tuple match {
  case (1, x) => s"二元组,第一个元素为 1,第二个:$x"
  case (x, y, z) => s"三元组:($x, $y, $z)"
  case _ => "非目标元组"
}

println(matchTuple((1, "scala")))  // 输出:二元组,第一个元素为 1,第二个:scala
println(matchTuple((2, 3, 4)))     // 输出:三元组:(2, 3, 4)
(3)Array 匹配

scala

def matchArray(arr: Array[Int]): String = arr match {
  case Array(1) => "单元素数组 [1]"
  case Array(1, _*) => "以 1 开头的数组"  // _* 匹配任意数量的后续元素
  case Array(x, y) => s"二元数组:[$x, $y]"
  case _ => "其他数组"
}

println(matchArray(Array(1,2,3)))  // 输出:以 1 开头的数组
println(matchArray(Array(5,6)))    // 输出:二元数组:[5, 6]

5. 样例类(Case Class)匹配

样例类是 Scala 专为模式匹配设计的类,匹配时可直接提取属性,是面向对象 + 模式匹配的核心用法:

scala

// 定义样例类
case class Person(name: String, age: Int)
case class Student(name: String, grade: Int)
case object Empty  // 样例对象(无属性)

def matchCaseClass(obj: Any): String = obj match {
  case Person(name, age) if age > 18 => s"成年人:$name,年龄:$age"
  case Person(name, age) => s"未成年人:$name,年龄:$age"
  case Student(name, 9) => s"九年级学生:$name"
  case Empty => "空对象"
  case _ => "非目标类型"
}

println(matchCaseClass(Person("张三", 20)))  // 输出:成年人:张三,年龄:20
println(matchCaseClass(Student("李四", 9)))  // 输出:九年级学生:李四
println(matchCaseClass(Empty))               // 输出:空对象

6. 变量绑定(@ 符号)

匹配模式的同时,将整个匹配结果绑定到变量,方便后续使用:

scala

def matchBind(list: List[Int]): String = list match {
  case l @ List(1, _*) => s"匹配到列表:$l(以 1 开头)"  // l 绑定整个列表
  case Person(name, _) @ Person(_, 25) => s"年龄 25 的人:$name,对象:$Person"
  case _ => "未绑定"
}

println(matchBind(List(1,2,3)))  // 输出:匹配到列表:List(1, 2, 3)(以 1 开头)

7. 偏函数中的模式匹配

偏函数(PartialFunction)是只对部分输入值有定义的函数,核心是 isDefinedAt + apply,语法上可通过 case 直接定义:

scala

// 定义偏函数:仅处理 Int 类型且值 > 0 的输入
val positive: PartialFunction[Any, String] = {
  case i: Int if i > 0 => s"正数:$i"
}

// 使用偏函数
println(positive.isDefinedAt(10))  // true
println(positive.isDefinedAt(-5))  // false
println(positive(10))              // 输出:正数:10

// 集合中使用偏函数(collect 方法)
val list = List(1, -2, "scala", 3.14, 5)
val result = list.collect(positive)
println(result)  // 输出:List(正数:1, 正数:5)

三、高级特性

1. 密封类(Sealed Class)

密封类限制子类必须定义在同一文件中,确保模式匹配的穷尽性(编译器可检查是否遗漏 case):

scala

sealed trait Shape  // 密封特质
case class Circle(r: Double) extends Shape
case class Square(side: Double) extends Shape

def matchShape(shape: Shape): Double = shape match {
  case Circle(r) => Math.PI * r * r  // 圆面积
  case Square(s) => s * s            // 正方形面积
  // 无 _ 也不会报错,因为编译器知道 Shape 只有两个子类
}

println(matchShape(Circle(2)))  // 输出:12.566370614359172

2. 匹配中赋值

模式匹配的结果可直接赋值给变量,简化代码:

scala

val (x, y) = (1, 2)  // 元组解构赋值
println(x)  // 1

val List(a, b, _) = List(3,4,5)  // 列表解构赋值
println(b)  // 4

val Person(name, age) = Person("王五", 30)  // 样例类解构赋值
println(name)  // 王五

四、常见注意事项

  1. 匹配顺序:优先匹配具体的 case,再匹配通用的(如 _),避免前面的 case 覆盖后面的;

  2. 穷尽性检查:使用密封类时,编译器会检查是否覆盖所有子类,非密封类建议加 _ 兜底;

  3. 泛型擦除:运行时无法区分 List[Int] 和 List[String],需结合类型标签(TypeTag)或守卫;

  4. 样例类的 copy 方法:匹配后可通过 copy 方法修改属性(样例类默认实现):

    scala

    val p = Person("赵六", 25)
    val p2 = p match {
      case Person(n, a) => p.copy(age = a + 1)
    }
    println(p2)  // Person(赵六,26)
    

五、总结

Scala 模式匹配是其核心特性之一,核心优势:

  • 支持多维度匹配(值、类型、结构、对象);
  • 表达式特性,可直接赋值;
  • 结合样例类 / 密封类,实现类型安全的分支逻辑;
  • 偏函数进一步简化 “部分匹配” 场景。

相比 Java 的 switch,Scala 模式匹配更灵活、更贴近业务逻辑,是处理多分支判断的首选方式。