密封类(Sealed Class)

Java 17 引入了密封类(Sealed Class),用来限制类的继承关系。

为什么要限制继承?我见过太多项目,因为子类太多导致代码难以维护。一个接口有几十个实现类,switch 语句要写几十个 case,每次加新实现都要改很多地方。

密封类就是为了解决这个问题:在编译期确保只有指定的类可以继承/实现

今天我们就来把密封类彻底讲透。

一、为什么需要密封类

1.1 继承失控的问题

// 一个 Payment 接口可能有几十种实现
interface Payment {
    double getAmount();
}

// 后来者不断添加新的实现...
class AliPay implements Payment { }
class WechatPay implements Payment { }
class CreditCard implements Payment { }
class BankTransfer implements Payment { }
// ... 10 年后有 50 种实现

1.2 switch 的问题

double calculateFee(Payment payment) {
    switch (payment) {
        case AliPay p -> p.getAmount() * 0.01;
        case WechatPay p -> p.getAmount() * 0.005;
        case CreditCard p -> p.getAmount() * 0.02;
        // ... 要覆盖所有情况
        // 但编译器无法强制你覆盖所有
        default -> 0;
    }
}

1.3 密封类的解决方案

// 密封 Payment,只允许特定的几种实现
sealed interface Payment permits AliPay, WechatPay, CreditCard {
    double getAmount();
}

// 这些类必须明确声明自己的继承方式
final class AliPay implements Payment {  // final:不能再被继承
    public double getAmount() { return 0; }
}

sealed class WechatPay implements Payment {  // sealed:可以被继承,但只能继承指定类
    public double getAmount() { return 0; }
}

non-sealed class CreditCard implements Payment {  // non-sealed:解除密封,可以被任何类继承
    public double getAmount() { return 0; }
}

二、密封类的语法

2.1 密封接口

// sealed:密封
// permits:允许的实现类
sealed interface Payment permits AliPay, WechatPay, CreditCard {
    double getAmount();
}

2.2 密封类

// 密封类
sealed class Shape permits Circle, Rectangle, Triangle {
    // 类的实现
}

final class Circle extends Shape { }      // final:不能再继承
sealed class Rectangle extends Shape { } // sealed:可以继承,但要声明
non-sealed class Triangle extends Shape { } // non-sealed:解除密封

2.3 子类的修饰符规则

sealed class Parent permits A, B, C {
    static class A extends Parent { }    // ✅ static 内部类可以
    final class B extends Parent { }      // ✅ final:不能再继承
    sealed class C extends Parent permits C1, C2 { }  // ✅ sealed:可以继承
    non-sealed class D extends Parent { } // ✅ non-sealed:解除密封
}

三、密封类的检查机制

3.1 编译期检查

sealed interface Color permits Red, Green, Blue {}

// 尝试添加未声明的实现
class Yellow implements Color { }  // ❌ 编译错误!

编译器会强制检查,不在 permits 列表中的类不能继承/实现。

3.2 switch 的穷尽检查

String describe(Payment payment) {
    return switch (payment) {
        case AliPay p -> "AliPay";
        case WechatPay p -> "WechatPay";
        case CreditCard p -> "CreditCard";
        // ✅ 编译器知道只有这三种,不需要 default
    };
}

编译器检查 switch 是否覆盖了所有密封类型的子类型。

3.3 不需要 default 的情况

sealed interface Expr permits Constant, Add, Multiply {}

// 只需要处理部分情况?
Expr expr = ...;

// ❌ 编译错误:没有穷尽所有情况
String result = switch (expr) {
    case Constant c -> "constant";
    // 缺少 Add 和 Multiply 的处理
};

如果 switch 不是穷尽的,编译器会报错。

四、【直观类比】

【直观类比】

密封类就像一个"家族族谱":

密封接口 Shape

    permits

    ┌────┴────┐
    │         │
 Circle    Rectangle
 (final)   (sealed)

          permits

          ┌────┴────┐
        Square   Rect1  Rect2
       (final)

只有族谱上登记的人才能继承,不在名单上的"外人"不能继承。

五、密封类的应用场景

5.1 有限状态机

sealed interface State permits Idle, Running, Paused, Stopped {
    default String status() {
        return this.getClass().getSimpleName();
    }
}

final class Idle implements State { }
final class Running implements State { }
final class Paused implements State { }
final class Stopped implements State { }

State state = new Running();
String s = switch (state) {
    case Idle i -> "空闲中";
    case Running r -> "运行中";
    case Paused p -> "已暂停";
    case Stopped s -> "已停止";
};
// 编译器确保覆盖所有状态

5.2 领域模型

sealed interface Result permits Success, Failure {
    // 通用方法
}

record Success(String data) implements Result {}
record Failure(String error) implements Result {}

Result r = process();
if (r instanceof Success(String data)) {
    System.out.println("Data: " + data);
} else if (r instanceof Failure(String error)) {
    System.out.println("Error: " + error);
}

5.3 API 设计

// 框架内部只允许特定扩展
sealed abstract class Validator permits RequiredValidator, 
                                     LengthValidator,
                                     PatternValidator {
    public abstract boolean validate(String value);
}

public final class RequiredValidator extends Validator {
    @Override
    public boolean validate(String value) {
        return value != null && !value.isBlank();
    }
}

// 用户不能自己添加新的 Validator
// 框架可以完全控制 Validator 的种类

六、密封类与 Record

6.1 Record 实现密封接口

sealed interface Response permits SuccessResponse, ErrorResponse {
    int getCode();
}

// Record 可以实现密封接口
record SuccessResponse(int code, String data) implements Response {}
record ErrorResponse(int code, String message) implements Response {}

6.2 密封 Record

Record 也可以是密封的:

sealed record Shape() permits Circle, Rectangle, Triangle {}

sealed record Circle(double radius) Shape {}
final record Rectangle(double width, double height) Shape {}
non-sealed record Triangle(double a, double b, double c) Shape {}

七、生产避坑

7.1 ❌ 错误示范:忘记声明所有子类

sealed interface Animal permits Dog, Cat { }  // 只声明了 Dog 和 Cat

class Bird implements Animal { }  // ❌ 编译错误:Bird 没有在 permits 中

// ✅ 正确做法:要么把 Bird 加到 permits
sealed interface Animal permits Dog, Cat, Bird { }

7.2 ❌ 错误示范:在 permits 列表中写不存在的类

sealed interface Color permits Red, Green, Blue, Yellow { }
// ❌ Yellow 类不存在!

7. ✅ 正确示范:规划好继承层次

// 先规划好有哪些子类
sealed interface Shape permits Circle, Rectangle, Triangle, Polygon { }

// Circle 是 final,不再被继承
final class Circle implements Shape { }

// Rectangle 可以被继承,但只能继承特定的子类
sealed class Rectangle implements Shape permits Square, NonSquare {
    // ...
}

// Square 是 final
final class Square extends Rectangle { }
non-sealed class NonSquare extends Rectangle { }

八、面试追问链

第一层:基础概念

面试官问:"密封类是什么?"

Java 17 引入的特性,用 sealed 修饰类和接口,用 permits 声明允许的子类。子类的修饰符必须是 finalsealednon-sealed 之一。

第二层:作用

面试官问:"密封类解决了什么问题?"

在编译期确保只有指定的类可以继承/实现,避免类被任意扩展。配合 switch 的穷尽检查,可以让编译器强制开发者处理所有情况。

第三层:与枚举的区别

面试官问:"密封类和枚举有什么区别?"

枚举是"有限实例",密封类是"有限类型"。枚举适合表示固定的几个值,密封类适合表示有多个子类变体的概念。密封类可以有自己的状态(字段),枚举不行。

【学习小结】

  • 密封类用 sealed 声明,permits 指定允许的子类
  • 子类必须是 finalsealednon-sealed
  • 编译器强制检查,防止未声明的继承
  • 配合 switch 的穷尽检查
  • 适合有限状态机、领域模型、API 设计