Skip to content

Sealed Classes

想象一下这个场景:你写了一个 Shape 接口,有 CircleRectangle 等实现类。后来别人又加了一个 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 解决了继承的三大问题:

  1. 不知道谁继承了:显式声明
  2. switch 漏掉情况:编译器检查
  3. 意外被继承:限制访问
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 中的类都会报错
    };
}

适合场景:

  • 领域模型的有限类型
  • 状态机的有限状态
  • 解析器的表达式类型
  • 任何「类型已知且有限」的场景

基于 VitePress 构建