1. 深入了解 record 类
在深入 record patterns 之前,先回顾一下什么是 record 类。
Record 是一种简洁、不可变的类,会自动生成 equals、hashCode、toString 方法,并为所有组件自动创建 getter。Record 类在 Java 16 引入,对厌倦了手写 DTO 的人来说可谓大礼包。
// 经典示例:具有两个字段的 Point
record Point(int x, int y) {}
创建对象并访问字段:
Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p); // Point[x=10, y=20]
为什么需要 Record Patterns
在 Java 21 之前,如果你想检查某个对象是否为某个 record,并获取其字段,就得写大量样板代码:通过 instanceof 检查类型、进行强制类型转换并调用 getter。
Object obj = new Point(5, 7);
if (obj instanceof Point) {
Point p = (Point) obj;
int x = p.x();
int y = p.y();
System.out.println("x=" + x + ", y=" + y);
}
如果 record 很复杂或是嵌套的,代码很快会变成“解包森林”。而且此前在 switch 中进行这种识别也并不方便。
Record patterns 允许在模式匹配中直接解构 record 类的值:既可以在 instanceof,也可以在 switch 中。这样代码更短、更安全也更清晰。
2. Record patterns 语法:最简单的示例
record Point(int x, int y) {}
Object obj = new Point(10, 20);
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
这里发生了什么?
- obj instanceof Point(int x, int y) —— 同时完成类型检查,并将组件立即解构到变量 x 和 y 中。
- 变量 x 和 y 只在 if 块内部可用。
结果:
x=10, y=20
注意:如果 obj 不是 Point 或其为 null,则 if 块不会执行,这些变量也不会被声明。
3. 在 switch 中使用 record patterns:简洁且安全
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
Object shape = new Point(3, 4);
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
default -> System.out.println("Unknown shape!");
}
- 在 case Point(int x, int y) 中,我们可以直接使用 x 和 y。
- 在 case Circle(Point center, int r) 中,可用的变量是 center 和 r。
结果:
Point: x=3, y=4
配合 sealed 继承层次,switch 可以做到穷尽匹配:如果遗漏某个分支,编译器会给出警告。
4. 嵌套的 record patterns:在解构中继续解构
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Line(new Point(1, 2), new Point(3, 4));
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}
结果:
Line from (1,2) to (3,4)
需要处理树、图或复杂结构吗?嵌套模式会是你的好伙伴。
5. 带守卫表达式(when)的 record patterns
Object obj = new Point(0, 100);
if (obj instanceof Point(int x, int y) && x == 0) {
System.out.println("点在 Y 轴上:y=" + y);
}
在 switch 中则通过 when 来实现:
switch (obj) {
case Point(int x, int y) when x == 0 -> System.out.println("在 Y 轴上:y=" + y);
case Point(int x, int y) when y == 0 -> System.out.println("在 X 轴上:x=" + x);
case Point(int x, int y) -> System.out.println("普通点");
default -> System.out.println("不是点");
}
6. 在真实应用中使用 record patterns
来一个实用示例——几何图形。
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
case Circle(Point(int x, int y), int radius) ->
System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
case Rectangle(Point(int x, int y), int width, int height) ->
System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
default -> System.out.println("Unknown shape");
}
}
调用示例:
printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));
结果:
Rectangle: topLeft=(5, 10), size=20x30
7. 使用 record patterns 的限制和细节
仅适用于 record 类。 Record patterns 只适用于真正的 record 类对象。
class NotARecord {
int a, b;
NotARecord(int a, int b) { this.a = a; this.b = b; }
}
// 错误!这不是 record
// if (obj instanceof NotARecord(int a, int b)) { ... }
组件的数量与类型必须匹配。 模式必须与 record 类的组件一一对应。
record Pair(int a, String b) {}
// 错误:类型不匹配
// if (obj instanceof Pair(String a, int b)) { ... }
变量仅在块内可见。 在模式中声明的名称,仅在匹配成功的特定 if 或 case 的主体中可见。
8. 嵌套模式:树结构示例
sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}
record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
return switch (expr) {
case NumberExpr(int value) -> value;
case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
};
}
使用示例:
Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14
这里我们直接解构 NumberExpr(int value)、PlusExpr(Expr left, Expr right) 和 MinusExpr(Expr left, Expr right)——代码因此更简洁、更具表现力。
9. 表格:使用与不使用 record patterns 的模式匹配对比
| 方式 | 手动类型转换 | 使用 record patterns 的模式匹配 |
|---|---|---|
| 类型检查 | |
|
| 类型转换 | |
不需要 |
| 访问字段 | |
|
| 嵌套解构 | 大量手写代码 | 嵌套模式 |
| 在 switch 中使用 | 不便/不可行 | 简单、简洁 |
10. 使用 record patterns 的常见错误
错误 #1:试图对非 record 类使用 record pattern。 如果你写的是普通类,却试图把它“解构”为 record,编译器不会允许。Record patterns 只对真正的 record 类生效。
错误 #2:组件数量或类型不匹配。 模式必须完全匹配 record 的组件签名。例如,对于 record Pair(int a, String b) 不能写 Pair(int x, int y)。
错误 #3:尝试在块之外使用模式变量。 模式中声明的名称仅在对应的 if/case 块内可用;离开该块后,这些变量不存在。
错误 #4:在旧版 JDK 上使用 record patterns。 支持是在 Java 21 中加入的。在 Java 17 及以下版本会得到语法错误。请检查 JDK 版本以及 IDE 的支持情况。
错误 #5:过度深的嵌套模式。 嵌套能力很强,但不要不必要地复杂化:过深的结构会降低可读性并提高出错风险。
GO TO FULL VERSION