本文目录导读:

这是一个很好的问题,Java和Scala在模式匹配上的核心思想是相通的——都是基于代数数据类型(ADT)和密封类/特质的结构化数据分解。
但Scala的模式匹配是这个语言的核心杀手锏,而Java直到JDK 17+(尤其是JDK 21的Record Pattern)才算真正“能用”。Scala的模式匹配在表达能力、安全性和函数式编程深度上远超Java,而Java的模式匹配更侧重于在现有OOP框架下提供更安全的代码编写方式。
下面从几个关键维度对比:
核心语法与完备性
- Java:需要使用关键字
case和when,匹配时必须有default或其他分支覆盖所有可能性(编译器会检查)。 - Scala:使用关键字
case,编译器默认会检查模式是否穷尽(密封类),并且一次性处理多个条件非常自然。
示例:匹配一个形状
Java 21 (Record + Sealed Class):
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
public double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
// 如果不写default,编译器会报错,因为Shape未穷尽
default -> throw new IllegalStateException("Unexpected shape: " + shape);
};
}
Scala 3 (或2):
sealed trait Shape case class Circle(radius: Double) extends Shape case class Rectangle(width: Double, height: Double) extends Shape def area(shape: Shape): Double = shape match case Circle(r) => Math.PI * r * r case Rectangle(w, h) => w * h // Scala编译器知道Shape只有这两个子类,所以不需要default
点评: Scala 的 match 更简洁,sealed 的穷尽性检查更优雅。
解构与嵌套匹配
这是差距最大的地方。
Java:支持解构 Record,但嵌套解构写法比较繁琐,且模式中不能进行复杂的嵌套或守卫条件(guard)。
// Java:解构一个嵌套的容器
record Point(int x, int y) {}
record Line(Point start, Point end) {}
public void handle(Line line) {
// 只能解构一层,需要额外变量
switch (line) {
case Line(var start, var end) -> {
// 需要再单独处理start和end
System.out.println(start.x() + "," + start.y());
}
// 无法直接写成 case Line(Point(x1,y1), Point(x2,y2)) =>
}
}
Scala:支持深度嵌套解构和守卫条件。
case class Point(x: Int, y: Int)
case class Line(start: Point, end: Point)
def handle(line: Line): Unit = line match
case Line(Point(x1, y1), Point(x2, y2)) if x1 == x2 =>
println(s"垂直的线:$y1 -> $y2")
case _ => // 其他情况
点评: Scala 可以直接在模式中解构到最内层,并加上 if 守卫,Java 需要手动嵌套 if 语句或额外的 switch,代码量明显增加。
模式类型
| 模式类型 | Java 支持? | Scala 支持? |
|---|---|---|
| 常量模式 | 是(case 1, "hello") |
是 |
| 类型模式 | 是(case String s) |
是 |
| Record模式 | 是(JDK 21) | 是(case class 自动支持) |
| 守卫条件 | 是(case X when cond) |
是(if 关键字更灵活) |
| 可选(Option)模式 | 无原生;需要手动 isPresent() |
原生 Some(x) 和 None |
| 提取器(Unapply) | 不支持 | 极其强大 |
提取器是Scala的独门利器。
Scala 允许你为任何类(不仅仅是Case Class)定义 unapply 方法,这使得你可以对完全非定制的类(例如Java的 LocalDate、第三方库的类)进行模式匹配。
// Scala:为第三方类定义提取器
object Even {
def unapply(n: Int): Option[Int] = if (n % 2 == 0) Some(n) else None
}
val num = 4
num match {
case Even(x) => println(s"偶数:$x")
case _ => println("奇数")
}
Java 无法 做到这一点,Java的 switch 只能匹配 Record、Enum、String、整数和基本类型包装类,以及通过 instanceof 的模式(但无法自定义分解逻辑)。
集合与列表模式
- Java:没有原生的列表模式匹配,你需要写
if (list.size() == 2)get(0),或者用流。 - Scala:列表匹配是函数式编程的基石。
def describe(list: List[Int]): String = list match
case Nil => "空列表"
case head :: tail => s"头部是$head,尾部还有${tail.size}个元素"
// 或者匹配精确长度
case List(a, b, c) => s"正好三个元素:$a, $b, $c"
Java 无法用一行代码优雅地分解列表的头和尾。
编译时安全检查
两者都支持基本的穷尽性检查(sealed 类),但有细微差异:
- Java:当
switch的case覆盖所有sealed类的子类并包含default时,编译通过。Java 强制你写default,哪怕你知道已经穷尽了,这会引入隐藏的运行时错误(未来新增子类时,default会静默处理错误)。 - Scala:当
sealed类穷尽时,不需要也不能写default,未来新增子类,编译器会报告所有match不穷尽,让你逐个修改,这更安全。
Java 的 default 是一个反模式(在穷尽匹配语境下)。
何时选择哪种?
| 场景 | 推荐 |
|---|---|
| 维护大型现有Java项目,需要渐进式改进安全性 | Java 21+ 的模式匹配已经足够好,且向下兼容 |
| 新项目,语言政策允许,且团队熟悉函数式编程 | Scala 3 (或 Kotlin,Kotlin的模式匹配也很强,但不如Scala灵活) |
| 解析器、编译器、解释器开发 | Scala 几乎是标配,提取器和列表匹配无与伦比 |
| 处理复杂的树形结构或AST | Scala 深度嵌套匹配是核心优势 |
| 数据清洗、简单的条件分支 | Java 已经够用 |
Java模式匹配像是一把精确的手术刀——安全、可控、有边界(强制default)。 Scala模式匹配像是一把瑞士军刀——灵活、强大、能解构一切(自定义提取器),但需要更严格的编码纪律(避免滥用模式覆盖匹配)。
如果你的项目已经大量使用Lombok、Record、Sealed Class,Java 21的模式匹配是一个显著的进步,但如果你需要真正的数据结构分解、守卫条件、自定义提取器以及高阶函数的结合,Scala仍是王者。