第2章 现代 Java 语法:Record、Sealed Class 与模式匹配
本章学习目标
- 掌握 Record 类型的原理与最佳实践
- 理解密封类的设计意图与应用场景
- 熟练运用 switch 模式匹配与 Record Patterns
2.1 Record:告别样板代码
Java 中大量类只是数据载体——字段、构造器、getter、equals、hashCode、toString。Record 将这一切浓缩为一行声明。
// Java 17+:Record 声明
public record Point(double x, double y) {}
// 编译器自动生成:
// - private final double x, y
// - 全参构造器 Point(double x, double y)
// - public double x(), public double y() (注意:不是 getX())
// - equals(), hashCode(), toString()
Record 的使用
Point p1 = new Point(1.0, 2.0);
Point p2 = new Point(1.0, 2.0);
System.out.println(p1.x()); // 1.0
System.out.println(p1.equals(p2)); // true(基于值比较)
System.out.println(p1); // Point[x=1.0, y=2.0]
Record 的约束
| 约束 | 说明 |
|---|---|
| 字段不可变 | 所有字段都是 private final |
| 不能继承 | Record 隐式继承 java.lang.Record |
| 可以实现接口 | record Pair(int a, int b) implements Comparable<Pair> |
| 可以添加自定义方法 | 可以写业务方法,但不能修改字段 |
| 可以添加紧凑构造器 | 用于参数校验 |
紧凑构造器
public record Range(int start, int end) {
// 紧凑构造器:省略参数列表(与默认构造器签名一致时)
public Range {
if (start > end) throw new IllegalArgumentException(
"start(" + start + ") 不能大于 end(" + end + ")");
}
// 业务方法
public int size() { return end - start; }
public boolean contains(int value) { return value >= start && value < end; }
}
2.2 Sealed Classes:控制继承层次
在面向对象设计中,有时你希望精确控制哪些类可以继承某个接口或抽象类。密封类提供了这种能力。
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
}
// 子类必须是 final、sealed 或 non-sealed 之一
public record Circle(double radius) implements Shape {
@Override public double area() { return Math.PI * radius * radius; }
}
public record Rectangle(double width, double height) implements Shape {
@Override public double area() { return width * height; }
}
public final class Triangle implements Shape {
private final double a, b, c;
public Triangle(double a, double b, double c) {
this.a = a; this.b = b; this.c = c;
}
@Override public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s-a) * (s-b) * (s-c));
}
}
子类修饰符要求
| 修饰符 | 含义 |
|---|---|
final |
终结继承链,不能再被继承 |
sealed |
继续限制,必须声明自己的 permits |
non-sealed |
开放继承,任何人都可以继承这个子类 |
2.3 Pattern Matching for switch
Java 21 将模式匹配从 instanceof 扩展到 switch,并与密封类完美结合。
// Java 21+:switch 模式匹配
public static String describe(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,半径: " + c.radius();
case Rectangle r -> "矩形,面积: " + r.area();
case Triangle t -> "三角形,面积: " + t.area();
// 编译器知道 Shape 只有这三个子类型
// 无需 default 分支!(密封类的完整性检查)
};
}
带条件的模式匹配(Guarded Patterns)
public static String classify(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "正整数: " + i;
case Integer i when i == 0 -> "零";
case Integer i -> "负整数: " + i;
case String s when s.isEmpty() -> "空字符串";
case String s -> "字符串: " + s.substring(0, Math.min(s.length(), 20));
case null -> "null";
default -> "未知类型: " + obj.getClass().getName();
};
}
2.4 Record Patterns(记录模式)
Java 21 允许在 switch 中解构 Record,直接访问其字段。
public record Address(String city, String street) {}
public record Person(String name, Address address) {}
public static String format(Person person) {
return switch (person) {
// 解构 Record:直接绑定 name 和 address 的字段
case Person(var name, Address(var city, var street)) ->
name + " 住在 " + city + " " + street;
};
}
// 嵌套解构
public record Line(Point start, Point end) {}
public static double length(Line line) {
return switch (line) {
case Line(Point(var x1, var y1), Point(var x2, var y2)) ->
Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
};
}
2.5 数据导向编程(Data-Oriented Programming)
Record + Sealed Class + Pattern Matching 三者组合形成了一种新的编程范式——数据导向编程:
- Record 定义数据形状
- Sealed Class 定义数据变体
- Pattern Matching 根据数据形状分派逻辑
// 用数据导向编程处理 JSON-like 结构
sealed interface JsonValue permits JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {}
record JsonString(String value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonBoolean(boolean value) implements JsonValue {}
record JsonNull() implements JsonValue {}
record JsonArray(List<JsonValue> elements) implements JsonValue {}
record JsonObject(Map<String, JsonValue> fields) implements JsonValue {}
// 递归处理
public static String prettyPrint(JsonValue value, int indent) {
return switch (value) {
case JsonString s -> """ + s.value() + """;
case JsonNumber n -> String.valueOf(n.value());
case JsonBoolean b -> String.valueOf(b.value());
case JsonNull n -> "null";
case JsonArray a -> "[...]"; // 省略递归逻辑
case JsonObject o -> "{...}"; // 省略递归逻辑
};
}
2.6 常见陷阱
⚠️ 陷阱 1:Record 的访问器方法名不是
getX(),而是x()Point p = new Point(1, 2); p.x(); // ✅ 正确 p.getX(); // ❌ 编译错误
⚠️ 陷阱 2:密封类的
permits列表必须与子类在同一模块/包中
- 如果密封类和子类在同一个模块中,
permits可以省略(编译器自动推断)- 如果跨模块,必须显式声明
⚠️ 陷阱 3:switch 模式匹配中,
case null必须显式处理
- 旧版 switch 遇到 null 会抛 NPE
- 新版 switch 必须写
case null -> ...或使用default兜底
2.7 小结
Record、Sealed Class 和 Pattern Matching 是 Java 类型系统的三大增强。它们共同构成了"数据导向编程"的基础,让 Java 能够像 Kotlin 的 data class + when 表达式一样优雅地处理数据分派逻辑。
2.8 面试实战题
题目 1:Record 与普通 POJO 类有什么区别?什么时候用 Record?
参考答案:
核心区别:
- 不可变性:Record 的字段都是
final的,POJO 可以是可变的 - 样板代码:Record 自动生成构造器、equals、hashCode、toString
- 继承限制:Record 隐式继承
java.lang.Record,不能再继承其他类 - 访问器命名:Record 用
x()而非getX()
使用场景:
- 适合 Record:DTO、值对象、配置类、Map 的 Key
- 不适合 Record:JPA Entity(需要可变字段和无参构造器)、需要继承的类
题目 2:密封类解决了什么问题?请举一个实际应用场景。
参考答案:
密封类解决的问题:限制继承层次,让编译器能穷举所有子类型。
实际场景——支付系统中的支付方式:
sealed interface PaymentMethod permits CreditCard, Alipay, WechatPay {}
支付系统只支持这 3 种方式,新增支付方式需要显式修改密封类,而不是随意扩展。配合 switch 模式匹配,编译器可以确保所有支付方式都被处理。
题目 3:Pattern Matching for switch 相比传统 switch 有什么优势?
参考答案:
- 类型安全:传统 switch 只能匹配基本类型和 String/Enum,新 switch 可以匹配任意对象类型
- 变量绑定:
case Circle c ->自动完成类型检查和变量绑定,无需手动转型 - 完整性检查:对密封类,编译器能检查 switch 是否覆盖了所有子类型,遗漏会报编译错误
- Guarded Patterns:
case Integer i when i > 0支持条件判断,减少嵌套
题目 4:什么是数据导向编程?与面向对象编程有什么区别?
参考答案:
数据导向编程(DOP)是一种将数据形状与行为分离的编程范式:
- OOP:数据和行为封装在同一个类中(
shape.area()) - DOP:数据用 Record/Sealed Class 定义形状,行为用 switch 模式匹配实现
DOP 的优势:
- 新增数据变体容易:添加新的 Record 类型,switch 会编译报错提醒
- 新增操作容易:添加新的 switch 方法,不影响数据定义
- 更适合数据处理:JSON 解析、协议处理等场景,DOP 更清晰
题目 5:Record Patterns 如何实现嵌套解构?
参考答案:
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Line line = new Line(new Point(0, 0), new Point(3, 4));
// 嵌套解构:一层 case 提取所有字段
switch (line) {
case Line(Point(var x1, var y1), Point(var x2, var y2)) -> {
double distance = Math.sqrt(Math.pow(x2-x1,2) + Math.pow(y2-y1,2));
System.out.println("距离: " + distance);
}
}
Record Patterns 在匹配时自动调用 accessor 方法(line.start()、start.x()),将嵌套结构展开为扁平变量。这是一种"声明式解构",比手动逐层调用更清晰。
更多推荐

所有评论(0)