Java 密封类与接口(Sealed Classes)详解
全面覆盖 Sealed Classes:从基础语法到代数数据类型建模,从模式匹配穷举到设计模式应用
适用版本:JDK 15(预览)→ JDK 17(正式)→ JDK 21+
一、密封类概述
1.1 什么是密封类
密封类(Sealed Classes)是 JDK 17 正式引入的语言特性,允许类或接口精确控制哪些类可以继承或实现它。
java
// 传统继承:任何类都可以继承
public abstract class Shape { }
// 任何人都可以写:public class Pentagon extends Shape { }
// 密封继承:只有 permits 中的类可以继承
public sealed class Shape permits Circle, Rectangle, Triangle { }
// 编译错误:public class Pentagon extends Shape { }
1.2 解决的核心问题
| 问题 | 传统方式 | 密封类 |
|---|---|---|
| 类型层次不可控 | 任何类可继承 | 只有许可类可继承 |
| 模式匹配无法穷举 | 必须写 default | 编译器验证穷举 |
| 建模意图不明确 | 靠文档约定 | 代码层面表达 |
| ADT 无法表达 | 模拟实现 | 原生支持 |
1.3 版本演进
| 版本 | 状态 |
|---|---|
| JDK 15 | 预览 |
| JDK 16 | 第二次预览 |
| JDK 17 | 正式标准化 |
二、基本语法
2.1 密封类
java
// 密封类:只有 Circle、Rectangle、Triangle 可以继承
public sealed class Shape permits Circle, Rectangle, Triangle {
public abstract double area();
}
2.2 密封接口
java
// 密封接口:更常用(因为接口可以多实现)
public sealed interface PaymentResult permits PaymentSuccess, PaymentFailed, PaymentPending {
String message();
}
2.3 permits 子句规则
java
// 方式1:显式列出所有子类
sealed class Shape permits Circle, Rectangle, Triangle {}
// 方式2:省略 permits(子类必须在同一文件中)
// Shape.java 文件中:
sealed class Shape {}
final class Circle extends Shape {} // OK,同一文件
final class Rectangle extends Shape {} // OK,同一文件
// 方式3:子类在不同包中(需要在同一模块中)
// 模块 module.geometry 中:
sealed class Shape permits com.other.Circle {} // 需要同一模块
2.4 子类的三种选择
java
sealed class Animal permits Dog, Cat, Bird {
abstract void speak();
}
// 选择1:final --- 终止继承链
final class Dog extends Animal {
@Override void speak() { System.out.println("Woof!"); }
}
// 选择2:sealed --- 继续密封
sealed class Cat extends Animal permits Lion, Tiger {
@Override void speak() { System.out.println("Meow!"); }
}
// 选择3:non-sealed --- 开放继承
non-sealed class Bird extends Animal {
@Override void speak() { System.out.println("Tweet!"); }
}
// Bird 是 non-sealed,任何类都可以继承
class Sparrow extends Bird {} // OK
class Eagle extends Bird {} // OK
// Cat 是 sealed,只有 Lion 和 Tiger 可以继承
final class Lion extends Cat {
@Override void speak() { System.out.println("Roar!"); }
}
final class Tiger extends Cat {
@Override void speak() { System.out.println("Grrr!"); }
}
三、密封类与模式匹配
3.1 switch 穷举检查
java
sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
}
record Circle(double radius) implements Shape {
@Override public double area() { return Math.PI * radius * radius; }
}
record Rectangle(double width, double height) implements Shape {
@Override public double area() { return width * height; }
}
record Triangle(double a, double b, double c) implements Shape {
@Override
public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
}
// 编译器穷举检查:不需要 default!
// 如果新增一个 Square implements Shape,编译器会在这里报错
static String describe(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,半径: " + c.radius();
case Rectangle r -> "矩形,宽: " + r.width() + ",高: " + r.height();
case Triangle t -> "三角形,边: " + t.a() + ", " + t.b() + ", " + t.c();
// 没有 default --- 编译器知道已覆盖所有可能
};
}
3.2 Record Patterns 解构
java
// JDK 21:Record Patterns 与密封类组合
sealed interface Expr permits Num, Add, Mul {}
record Num(double value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
// 解构 + 穷举
String prettyPrint(Expr expr) {
return switch (expr) {
case Num(var v) -> String.valueOf(v);
case Add(var l, var r) -> "(" + prettyPrint(l) + " + " + prettyPrint(r) + ")";
case Mul(var l, var r) -> "(" + prettyPrint(l) + " * " + prettyPrint(r) + ")";
};
}
// (2 + 3) * 4
Expr example = new Mul(new Add(new Num(2), new Num(3)), new Num(4));
System.out.println(prettyPrint(example)); // ((2.0 + 3.0) * 4.0)
四、代数数据类型(ADT)建模
4.1 JSON 值类型
java
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> properties) implements JsonValue {}
// 类型安全的 JSON 渲染
String render(JsonValue value) {
return switch (value) {
case JsonString(var s) -> "\"" + s + "\"";
case JsonNumber(var n) -> String.valueOf(n);
case JsonBoolean(var b) -> String.valueOf(b);
case JsonNull() -> "null";
case JsonArray(var elems) ->
"[" + elems.stream().map(this::render).collect(Collectors.joining(", ")) + "]";
case JsonObject(var props) ->
"{" + props.entrySet().stream()
.map(e -> "\"" + e.getKey() + "\": " + render(e.getValue()))
.collect(Collectors.joining(", ")) + "}";
};
}
4.2 状态机
java
// 订单状态机
sealed interface OrderState permits Created, Paid, Shipped, Delivered, Cancelled {
String description();
}
record Created() implements OrderState {
@Override public String description() { return "订单已创建"; }
}
record Paid(String paymentId) implements OrderState {
@Override public String description() { return "已支付: " + paymentId; }
}
record Shipped(String trackingNo) implements OrderState {
@Override public String description() { return "已发货: " + trackingNo; }
}
record Delivered(LocalDateTime time) implements OrderState {
@Override public String description() { return "已送达: " + time; }
}
record Cancelled(String reason) implements OrderState {
@Override public String description() { return "已取消: " + reason; }
}
// 状态转换验证
OrderState next(OrderState current, OrderAction action) {
return switch (current) {
case Created c -> switch (action) {
case Pay p -> new Paid(p.paymentId());
case Cancel x -> new Cancelled(x.reason());
default -> throw new IllegalStateException("非法状态转换");
};
case Paid p -> switch (action) {
case Ship s -> new Shipped(s.trackingNo());
default -> throw new IllegalStateException("非法状态转换");
};
// ... 其他状态
default -> throw new IllegalStateException("终态不可转换");
};
}
4.3 结果类型(Either)
java
// 函数式结果类型
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(Exception error) implements Result<T> {}
// 使用
<T> Result<T> tryExecute(Supplier<T> supplier) {
try {
return new Success<>(supplier.get());
} catch (Exception e) {
return new Failure<>(e);
}
}
// 处理结果
<T, R> R handle(Result<T> result, Function<T, R> onSuccess, Function<Exception, R> onFailure) {
return switch (result) {
case Success<T>(var value) -> onSuccess.apply(value);
case Failure<T>(var error) -> onFailure.apply(error);
};
}
// 实际使用
Result<Integer> result = tryExecute(() -> 42 / 0);
String message = handle(result,
v -> "成功: " + v,
e -> "失败: " + e.getMessage()
);
4.4 命令模式
java
sealed interface Command permits StartCmd, StopCmd, StatusCmd, RestartCmd {
String serviceName();
}
record StartCmd(String serviceName) implements Command {}
record StopCmd(String serviceName) implements Command {}
record StatusCmd(String serviceName) implements Command {}
record RestartCmd(String serviceName, boolean force) implements Command {}
// 命令处理器
String execute(Command cmd) {
return switch (cmd) {
case StartCmd(var name) -> "启动服务: " + name;
case StopCmd(var name) -> "停止服务: " + name;
case StatusCmd(var name) -> "查询状态: " + name;
case RestartCmd(var name, var force) ->
"重启服务: " + name + (force ? "(强制)" : "");
};
}
五、密封类与反射
java
// 获取密封类信息
Class<?> clazz = Shape.class;
// 检查是否是密封类
System.out.println(clazz.isSealed()); // true
// 获取允许的子类
Class<?>[] permitted = clazz.getPermittedSubclasses();
for (Class<?> c : permitted) {
System.out.println(c.getName());
// Circle, Rectangle, Triangle
}
六、最佳实践
6.1 何时使用密封类
| 场景 | 是否适合 |
|---|---|
| 有限的类型集合(状态、结果) | ✅ 非常适合 |
| 代数数据类型建模 | ✅ 非常适合 |
| 模式匹配穷举 | ✅ 非常适合 |
| 需要扩展的框架接口 | ❌ 不适合 |
| 第三方库需要实现的接口 | ❌ 不适合 |
| 类型数量不确定 | ❌ 不适合 |
6.2 设计原则
java
// 原则1:密封类搭配不可变实现(Record)
sealed interface Event permits UserCreated, UserDeleted, UserUpdated {}
record UserCreated(String id, String name) implements Event {}
record UserDeleted(String id) implements Event {}
record UserUpdated(String id, Map<String, Object> changes) implements Event {}
// 原则2:密封接口优于密封类(更灵活)
// 接口可以多实现,类只能单继承
// 原则3:配合 switch 表达式使用
// 避免 if-else 链
// 原则4:在模块边界内使用
// 密封类最适合在模块内部定义类型层次
总结:密封类与 Record、Pattern Matching 共同构成现代 Java 类型系统的三件套。密封类控制"谁可以实现",Record 定义"数据长什么样",模式匹配处理"如何分支"。三者组合使 Java 具备了类似函数式语言的代数数据类型建模能力。
