从 Java 14 开始引入的模式匹配,在 Java 21 中迎来了里程碑式的提升——记录模式(Record Patterns)正式转正。这项特性让开发者能够直接在 instanceof 和 switch 中解构记录对象,将类型检查、类型转换和字段提取合并为一步操作。结合 密封类(Sealed Classes) 和增强的 switch 表达式,Java 现在能够以函数式编程风格清晰表达复杂的数据驱动逻辑。本文将从基础语法讲起,通过三个完整的实战案例,带你彻底掌握这套简化代码的利器。
一、模式匹配的演进:从传统 instanceof 到记录模式
在传统的 Java 代码中,我们经常需要编写“检查类型 → 强制转换 → 使用变量”的样板代码,例如:
// 传统写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
Java 16 引入了 instanceof 模式匹配,将转换与绑定合并:
// Java 16 之后
if (obj instanceof String s) {
System.out.println(s.length());
}
Java 17 将该模式扩展到了 switch 表达式,实现了更简洁的多分支类型判断。而 Java 21 的 记录模式 更进一步:如果对象是记录(record),可以直接在模式中提取其组件值。
record Point(int x, int y) {}
// Java 21 记录模式
void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x + y);
}
}
这种能力让开发者无需手动调用 point.x(),模式匹配自动完成解构,代码既简洁又安全。
二、记录模式的基础语法与嵌套解构
记录模式不仅适用于单层记录,还可以嵌套使用,轻松处理树形结构。例如定义一个包含线段的记录:
record Point(int x, int y) {}
record Line(Point start, Point end) {}
// 嵌套记录模式
void printCoordinates(Object obj) {
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.printf("线段从 (%d,%d) 到 (%d,%d)%n", x1, y1, x2, y2);
}
}
在这个例子中,Line 被解构成两个 Point,而每个 Point 又被解构成 x 和 y,一步到位。如果没有记录模式,我们需要写多层 instanceof 和多行获取组件的方法调用。
在 switch 中使用记录模式同样直观:
String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == 0 && y == 0 -> "原点";
case Point(int x, int y) when x == y -> "对角线上的点";
case Point(var x, var y) -> "点(" + x + "," + y + ")";
default -> "未知类型";
};
}
这里使用了 when 子句进行附加条件约束,并且可以用 var 自动推断组件类型。
三、实战案例一:几何图形面积计算器
我们使用记录和密封类构建一个几何形状体系,然后利用 switch 模式匹配实现计算面积。首先定义密封接口和各形状记录:
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
现在编写面积计算函数,无需任何 if-else 或 visitor 模式:
double area(Shape shape) {
return switch (shape) {
case Circle(double r) -> Math.PI * r * r;
case Rectangle(double w, double h) -> w * h;
case Triangle(double b, double h) -> 0.5 * b * h;
};
}
当新增形状时(例如添加 Square 记录),编译器会强制我们在 switch 中处理新分支,因为密封类限定了所有子类,确保了类型安全性。这是传统多态难以做到的。
四、实战案例二:处理 JSON 节点并格式化输出
在实际开发中,我们经常需要处理树形的 JSON 结构。利用记录模式和 switch,可以轻松编写一个递归的 JSON 格式化器。先定义 JSON 节点类型:
sealed interface JsonNode permits JsonString, JsonNumber, JsonObject, JsonArray {}
record JsonString(String value) implements JsonNode {}
record JsonNumber(double value) implements JsonNode {}
record JsonObject(Map<String, JsonNode> entries) implements JsonNode {}
record JsonArray(List<JsonNode> elements) implements JsonNode {}
递归打印 JSON 字符串的实现:
String format(JsonNode node) {
return switch (node) {
case JsonString(String s) -> """ + s + """;
case JsonNumber(double d) -> String.valueOf(d);
case JsonObject(Map<String, JsonNode> map) -> {
StringBuilder sb = new StringBuilder("{");
map.forEach((k, v) -> sb.append(""").append(k).append("":")
.append(format(v)).append(","));
if (!map.isEmpty()) sb.deleteCharAt(sb.length() - 1);
sb.append("}");
yield sb.toString();
}
case JsonArray(List<JsonNode> list) -> {
String elements = list.stream()
.map(this::format)
.collect(Collectors.joining(","));
yield "[" + elements + "]";
}
};
}
代码中没有 instanceof 和强制转换,每个分支直接获得了解构后的数据。这比使用访问者模式或一系列的 if-else 链要清晰得多。
五、实战案例三:简化抽象语法树(AST)操作
在编译器或规则引擎中,AST 节点通常也很适合用记录表示。我们以简单的算术表达式为例:
sealed interface Expr permits NumberExpr, AddExpr, MulExpr {}
record NumberExpr(int value) implements Expr {}
record AddExpr(Expr left, Expr right) implements Expr {}
record MulExpr(Expr left, Expr right) implements Expr {}
利用模式匹配递归计算表达式值:
int evaluate(Expr expr) {
return switch (expr) {
case NumberExpr(int v) -> v;
case AddExpr(Expr left, Expr right) -> evaluate(left) + evaluate(right);
case MulExpr(Expr left, Expr right) -> evaluate(left) * evaluate(right);
};
}
遍历表达式树时,记录模式和 switch 的组合让代码极度简洁。如果未来添加 SubExpr,代码会因密封类的穷尽性产生编译错误,避免遗漏。
六、模式匹配带来的性能优势
除了可读性,记录模式和解构还有性能上的好处。在传统的 instance-cast 代码中,JVM 需要执行类型检查,然后再检查记录组件是否匹配。随着模式匹配的成熟,JIT 编译器可以生成专门的匹配代码路径,减少分支预测失败。此外,在一次模式匹配中完成的多个条件检查会被优化为单一操作,避免了重复的 instanceof。
七、总结与最佳实践
- 优先使用记录:对于不可变的数据载体,record 比普通类更简洁,且天然支持模式匹配。
- 密封类 + switch 穷尽性:用密封类限定子类型,确保 switch 分支完整,编译器会帮你检查遗漏。
- 避免过度嵌套:虽然记录模式支持深层嵌套,但太长的模式会降低可读性,可适时拆分为子方法。
- 谨慎使用 var:在模式中使用
var可以简化书写,但显式类型能增加代码的自文档化程度。
Java 21 的记录模式和模式匹配不仅仅是语法糖,它们代表了 Java 向更声明式、更类型安全的编程范式的转变。通过本文的三个实战案例,你应该已经感受到这种转变带来的巨大生产力提升。下一次当你面对复杂的类型判断或数据解构时,不妨尝试用记录模式来重写,你的代码会因此变得更加优雅。

