📌 JDK 版本要求:本文代码基于 JDK 17+ 编写并验证。
-
record 自 JDK 14 起预览,JDK 16 正式发布;
-
sealed class 自 JDK 15 起预览,JDK 17(LTS)正式发布;
-
switch 对 sealed class 的穷尽性检查需 JDK 17+。
建议使用 JDK 17 或更高 LTS 版本(如 JDK 21) 运行示例。
今天来学习用 record 和 sealed class 实现 代数数据类型(ADT),写出编译器帮你兜底的模式匹配代码!发车!

一、场景引入:从“满屏 if-else”到“类型即文档”
你是不是也写过这样的状态机?
public void handleEvent(String eventType, Object data) {
if ("LOGIN".equals(eventType)) {
// 处理登录
} else if ("LOGOUT".equals(eventType)) {
// 处理登出
} else if ("PAYMENT".equals(eventType)) {
// 处理支付
} else {
throw new IllegalArgumentException("未知事件: " + eventType);
}
}
这段代码的问题:
- 字符串魔法值,易拼错
- 类型不安全(Object data)
- 新增事件时,容易漏改处理逻辑
运行效果:看似能跑,但一旦事件类型增加,维护成本指数级上升!
二、核心武器一:record —— 不可变数据的终极语法糖
record 是一种紧凑型类声明,专为“纯数据载体”设计。
示例:定义一个点
public record Point(int x, int y) {}
等价于手写 50 行代码,但只需一行!
使用:
Point p = new Point(3, 4);
System.out.println(p.x()); // → 输出:3
System.out.println(p); // → 输出:Point[x=3, y=4]
System.out.println(p.equals(new Point(3, 4))); // → 输出:true

自动生成:构造器、accessor、equals/hashCode/toString,且字段不可变!

三、核心武器二:sealed class —— 封闭继承,掌控子类
sealed class 允许你精确控制哪些类可以继承它!
定义封闭的“形状”体系:
public sealed interface Shape permits Circle, Rectangle, Triangle {}
具体实现(必须是 final、sealed 或 non-sealed):
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double a, double b, double c) implements Shape {}
关键特性:编译器知道 Shape 只有这三种子类型,为模式匹配提供基础。
四、终极组合:switch + sealed class = 穷尽性检查!
从 JDK 17+ 开始,switch 支持对 sealed class 进行穷尽性检查!
计算面积:
public static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> {
double s = (t.a() + t.b() + t.c()) / 2;
yield Math.sqrt(s * (s – t.a()) * (s – t.b()) * (s – t.c()));
}
}; // 注意:没有 default!
}
调用与输出:
Shape circle = new Circle(2.0);
Shape rect = new Rectangle(3.0, 4.0);
Shape tri = new Triangle(3.0, 4.0, 5.0);
System.out.printf("圆面积: %.2f%n", area(circle)); // → 输出:圆面积: 12.57
System.out.printf("矩形面积: %.2f%n", area(rect)); // → 输出:矩形面积: 12.00
System.out.printf("三角形面积: %.2f%n", area(tri)); // → 输出:三角形面积: 6.00
如果漏掉某个 case,编译直接报错!
新增子类(如 Square),所有 switch 必须更新,否则无法编译!
五、实战演示:构建“订单状态机”
1. 定义封闭状态类型
public sealed interface OrderStatus
permits Pending, Confirmed, Shipped, Cancelled {}
public record Pending() implements OrderStatus {}
public record Confirmed(String confirmTime) implements OrderStatus {}
public record Shipped(String trackingNumber) implements OrderStatus {}
public record Cancelled(String reason) implements OrderStatus {}
2. 安全处理状态
public static String getStatusMessage(OrderStatus status) {
return switch (status) {
case Pending p -> "订单待确认";
case Confirmed c -> "已确认,时间: " + c.confirmTime();
case Shipped s -> "已发货,单号: " + s.trackingNumber();
case Cancelled c -> "已取消,原因: " + c.reason();
};
}
调用与输出:
OrderStatus[] statuses = {
new Pending(),
new Confirmed("2026-02-10 10:00"),
new Shipped("SF123456789CN"),
new Cancelled("用户取消")
};
for (OrderStatus status : statuses) {
System.out.println(getStatusMessage(status));
}
输出:
订单待确认
已确认,时间: 2026-02-10 10:00
已发货,单号: SF123456789CN
已取消,原因: 用户取消
零字符串魔法值!零 NPE!零遗漏!
六、最佳实践:5 条黄金法则
用 record 表示纯数据
如 DTO、配置项、事件载荷、几何对象等。
用 sealed class 表示有限状态/变体
如状态机、AST 节点、协议消息类型。
配合 switch 表达式实现穷尽处理
利用编译器检查,杜绝逻辑遗漏。
不要给 record 加业务方法
它是值对象,不是行为载体。复杂逻辑放在服务类中。
在 API 边界使用,提升契约清晰度
比如返回 Result<T>(Success/Failure),比抛异常更函数式。
七、注意事项
| “record 能继承吗?” | ❌ 不能!它是 final 的 |
| “sealed class 能跨模块吗?” | ✅ 可以!用 permits 显式声明,或同一模块内自动允许 |
| “旧代码怎么兼容?” | ✅ record 本质仍是普通类,字节码兼容 |
八、代码在哪?
本篇涉及到的代码已上传至 GitHub:
https://github.com/iweidujiang/java-tricks-lab/tree/main/05-record-sealed-class
欢迎 star & fork,下期还有神操作!




