Sealed Classes
想象一下这个场景:你写了一个 Shape 接口,有 Circle、Rectangle 等实现类。后来别人又加了一个 Triangle,但你的 switch case 漏掉了,运行时抛了 MatchException。
Sealed Classes 就是来解决这个问题的——它让你显式声明哪些类可以继承,编译器帮你检查是否穷尽了所有情况。
基本语法
密封类
java
public sealed class Person permits Student, Teacher, Worker {
// 公共代码
}密封接口
java
public sealed interface Shape permits Circle, Rectangle, Square {
// 公共代码
}子类的密封模式
每个允许继承的子类必须声明自己的模式:
java
public sealed class Person permits Student, Teacher, Worker {}
// ✅ final:不能再被继承
public final class Student extends Person {}
// ✅ sealed:继续密封,但只能被指定类继承
public sealed class Teacher extends Person permits MathTeacher, EnglishTeacher {}
// ✅ non-sealed:解除密封,可以被任何类继承
public non-sealed class Worker extends Person {}┌─────────────────────────────────────────────────────────────────┐
│ Person 密封层次 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ sealed Person │
│ permits Student, Teacher, Worker │
│ / | \ │
│ / | \ │
│ final sealed non-sealed │
│ Student Teacher Worker │
│ | │
│ permits... │
│ MathTeacher, EnglishTeacher │
│ | │
│ final │
│ MathTeacher │
│ │
└─────────────────────────────────────────────────────────────────┘为什么要用 Sealed Classes
场景一:类型安全的 switch
java
public sealed interface Shape permits Circle, Rectangle, Square {}
// 使用密封类,编译器确保穷尽
public String getShapeName(Shape shape) {
return switch (shape) {
case Circle c -> "圆形";
case Rectangle r -> "矩形";
case Square s -> "正方形";
// 编译器会检查:是否穷尽了所有 permits 的类?
// 如果漏掉,编译报错
};
}场景二:领域模型
java
public sealed interface Order permits NewOrder, PaidOrder, ShippedOrder, DeliveredOrder, CancelledOrder {}
public final class NewOrder implements Order {}
public final class PaidOrder implements Order {}
public final class ShippedOrder implements Order {}
public final class DeliveredOrder implements Order {}
public final class CancelledOrder implements Order {}
// 每个订单状态的处理逻辑都清晰
public void handle(Order order) {
switch (order) {
case NewOrder n -> processNewOrder(n);
case PaidOrder p -> processPaidOrder(p);
case ShippedOrder s -> processShippedOrder(s);
case DeliveredOrder d -> processDeliveredOrder(d);
case CancelledOrder c -> processCancelledOrder(c);
}
}场景三:解析器模式
java
public sealed interface Expr permits NumberExpr, AddExpr, MulExpr {}
public record NumberExpr(int value) implements Expr {}
public record AddExpr(Expr left, Expr right) implements Expr {}
public record MulExpr(Expr left, Expr right) implements Expr {}
// 编译器确保每种表达式都有处理
public int evaluate(Expr expr) {
return switch (expr) {
case NumberExpr n -> n.value();
case AddExpr a -> evaluate(a.left()) + evaluate(a.right());
case MulExpr m -> evaluate(m.left()) * evaluate(m.right());
};
}与 Records 配合
Records 是天然适合密封类的搭档:
java
public sealed interface Shape permits Circle, Rectangle, Square, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Square(double side) implements Shape {}
public record Triangle(double base, double height) implements Shape {}
public class ShapeDemo {
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 Square s -> s.side() * s.side();
case Triangle t -> t.base() * t.height() / 2;
};
}
public static void main(String[] args) {
Shape c = new Circle(5.0);
Shape r = new Rectangle(3.0, 4.0);
System.out.println(area(c)); // 78.54
System.out.println(area(r)); // 12.0
}
}嵌套密封类
密封类可以嵌套声明:
java
public sealed class Container permits Container.Empty, Container.Loading, Container.Full {}
public final class Empty extends Container {}
public sealed class Loading extends Container permits Container.Loading.InTransit, Container.Loading.AtDock {}
public non-sealed class Full extends Container {}
public sealed class Loading.InTransit extends Loading {}
public final class Loading.AtDock extends Loading {}反射检查
运行时可以检查密封关系:
java
public class SealedReflection {
public static void main(String[] args) {
Class<?> clazz = Worker.class;
// 检查是否密封
System.out.println("Is sealed: " + clazz.isSealed()); // true
// 获取允许的父类/接口
Class<?> permitted = clazz.getPermittedSubclasses()[0];
System.out.println("Permitted by: " + permitted.getName());
// 检查是否有密封修饰符
System.out.println("Modifiers: " + Modifier.toString(clazz.getModifiers()));
}
}与普通继承的区别
| 特性 | 普通继承 | Sealed Classes |
|---|---|---|
| 控制谁继承 | ❌ | ✅ |
| 编译器穷尽检查 | ❌ | ✅ |
| 运行时信息 | 运行时才知道 | 编译时就确定 |
| 灵活性 | 高 | 受限 |
| 安全性 | 低 | 高 |
常见模式
状态机模式
java
public sealed interface State permits State.Idle, State.Running, State.Paused, State.Stopped {}
public final class State.Idle extends State {}
public final class State.Running extends State {}
public final class State.Paused extends State {}
public final class State.Stopped extends State {}访问者模式
java
public sealed interface Expr permits Const, Add, Mul, Neg {}
public record Const(int value) implements Expr {}
public record Add(Expr left, Expr right) implements Expr {}
public record Mul(Expr left, Expr right) implements Expr {}
public record Neg(Expr expr) implements Expr {}
public interface Visitor<R> {
R visit(Const c);
R visit(Add a);
R visit(Mul m);
R visit(Neg n);
}小结
Sealed Classes 解决了继承的三大问题:
- 不知道谁继承了:显式声明
- switch 漏掉情况:编译器检查
- 意外被继承:限制访问
java
public sealed interface Shape permits Circle, Rectangle, Square {}
// 编译器帮你检查是否穷尽
String describe(Shape s) {
return switch (s) {
case Circle c -> "圆形";
case Rectangle r -> "矩形";
case Square sq -> "正方形";
// 漏掉任何 permits 中的类都会报错
};
}适合场景:
- 领域模型的有限类型
- 状态机的有限状态
- 解析器的表达式类型
- 任何「类型已知且有限」的场景
