JDK21密封类实战:sealed interface精确限制继承层次
2026/4/30大约 3 分钟
JDK21密封类实战:sealed interface精确限制继承层次
适读人群:Java中高级开发、关注现代Java特性的后端工程师 | 阅读时长:约14分钟
开篇故事
做过大型项目的人都遇到过这种问题:定义了一个抽象类或接口,原意只让几个特定子类实现,但过了半年,不知道谁偷偷加了第十几个子类,破坏了原有的设计意图。
更头疼的是,switch语句里要处理所有可能的子类,但你不知道到底有哪些子类,只能加一个catch-all的default分支——这让编译器无法帮你检测遗漏的case。
JDK17引入的密封类(Sealed Classes)正是为了解决这个问题。
一、密封类的核心概念
// 密封类/接口:精确声明允许哪些子类/实现类
public sealed interface Shape
permits Circle, Rectangle, Triangle { // 只有这三种
double area();
}
// 子类必须是以下三种之一:
// final(不可再继承)
// sealed(继续限制子类)
// non-sealed(开放继承,打破密封)
public final class Circle implements Shape { // final,不可再继承
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override public double area() { return Math.PI * radius * radius; }
}
public final class Rectangle implements Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width; this.height = height;
}
@Override public double area() { return width * height; }
}
public sealed interface Triangle extends Shape
permits EquilateralTriangle, RightTriangle { // 三角形只有两种
// ...
}二、与模式匹配switch结合(最大价值)
/**
* 密封类 + switch模式匹配:穷举检查
* 编译器知道所有可能的子类,漏写case会报编译错误
*/
public class ShapeRenderer {
public String describe(Shape shape) {
// JDK21的switch表达式 + 模式匹配
return switch (shape) {
case Circle c -> String.format("圆形,半径%.2f,面积%.2f", c.radius(), c.area());
case Rectangle r -> String.format("矩形,%.2f×%.2f,面积%.2f", r.width(), r.height(), r.area());
case Triangle t -> String.format("三角形,面积%.2f", t.area());
// 不需要default!编译器已知Shape只有这三种子类
// 如果漏写某个case,编译器会报错:"switch does not cover all possible input values"
};
}
public double calculateTax(Shape shape) {
// 守卫条件(when子句)
return switch (shape) {
case Circle c when c.area() > 100 -> c.area() * 0.1; // 大圆形税率10%
case Circle c -> c.area() * 0.05; // 小圆形税率5%
case Rectangle r -> r.area() * 0.08;
case Triangle t -> t.area() * 0.06;
};
}
}三、实战应用:结果类型(代替异常)
/**
* 密封类实现Result类型(函数式错误处理)
* 比抛异常更明确,强制调用方处理成功和失败两种情况
*/
public sealed interface Result<T> permits Result.Success, Result.Failure {
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String errorCode, String message) implements Result<T> {}
// 工厂方法
static <T> Result<T> success(T value) { return new Success<>(value); }
static <T> Result<T> failure(String code, String msg) { return new Failure<>(code, msg); }
}@Service
public class PaymentService {
public Result<PaymentReceipt> processPayment(PaymentRequest request) {
try {
if (request.amount().compareTo(BigDecimal.ZERO) <= 0) {
return Result.failure("INVALID_AMOUNT", "支付金额必须大于0");
}
PaymentReceipt receipt = gatewayClient.pay(request);
return Result.success(receipt);
} catch (GatewayException e) {
return Result.failure("GATEWAY_ERROR", e.getMessage());
}
}
}
// 调用方:编译器强制处理两种情况
Result<PaymentReceipt> result = paymentService.processPayment(request);
String response = switch (result) {
case Result.Success<PaymentReceipt> s -> "支付成功,流水号:" + s.value().transactionId();
case Result.Failure<PaymentReceipt> f -> "支付失败:" + f.message();
// 不需要default,编译器已穷举
};四、踩坑实录
坑1:密封类的子类必须与父类在同一包或模块
// 错误:子类在不同包
// com.example.shapes.Shape(sealed interface)
// com.example.impl.Circle(不在同一包)→ 编译错误!
// 解决:要么将子类移到同一包,要么使用模块系统(module-info.java)坑2:忘记声明子类为final/sealed/non-sealed
// 错误:子类没有声明密封性
public class Circle implements Shape { // 编译错误!
// 实现Shape的子类必须声明为 final、sealed 或 non-sealed
}
// 正确
public final class Circle implements Shape { ... }五、总结
密封类的核心价值:
- 限制继承:明确声明允许的子类,避免意外扩展
- 穷举检查:配合switch模式匹配,编译器帮你检测遗漏的case
- 建模代数数据类型:Result、Option等函数式编程常用模式的Java表达
最适合的场景:领域模型中子类集合固定且已知的情况(支付状态、形状类型、消息类型等)。
