密封类需显式列出允许继承的子类,子类必须与父类同模块并声明final/sealed/non-sealed,且仅当变量类型精确为密封类时才能支持switch穷尽检查。

密封类必须显式声明允许继承的子类
Java 17 的 sealed 类不是“默认封禁”,而是“白名单式放行”——你得亲手列出谁可以继承它,一个都不能少,也不能多。漏写、拼错类名、或子类不在同一模块(且未导出)都会直接编译失败。
常见错误现象:error: class X is not allowed to extend sealed class Y,往往是因为子类没在 permits 列表里,或者子类没用 final / sealed / non-sealed 明确表态。
-
permits后面跟的是**类名**,不是包名或路径,比如permits Dog, Cat,不能写成permits com.example.Dog - 所有被允许的直接子类,必须和密封父类在同一个模块中;如果跨模块,需在
module-info.java中用opens或exports配合requires显式授权 - 子类自己也得选好“身份”:用
final封死继承链,用sealed继续限定下级,或用non-sealed主动开放(但此举会削弱密封性,慎用)
子类必须用 final/sealed/non-sealed 明确继承意图
密封体系下,每个直接子类都得向编译器“签收”自己的继承策略。不声明就报错,没有默认行为。这不是风格问题,是语法强制。
使用场景:比如定义一个 Shape 密封类,只允许 Circle、Rect、Triangle 三种实现——其中 Circle 天然不可再扩展,就该用 final;Rect 可能有 RoundedRect 等变体,那就用 sealed 并继续 permits;而 Triangle 若想留给外部库自由实现(极少见),才用 non-sealed。
立即学习“Java免费学习笔记(深入)”;
-
final子类最安全,也是最常用的选择 -
sealed子类必须也带permits,形成可追溯的层级约束 -
non-sealed会打破密封边界,JVM 不再保证穷尽性,switch表达式无法启用exhaustive检查
配合 switch 表达式做穷尽检查时,类型必须是密封类本身
密封类真正的价值,在于让 switch 表达式能静态确认“已覆盖所有可能子类”。但这要求 switch 的操作数类型必须是密封类(如 Shape),而不是其父类(如 Object)或接口(如 Serializable)。
常见错误现象:写了完整 case 分支,却仍提示 switch expression does not cover all possible inputs——大概率是变量声明类型太宽泛,或用了泛型擦除后的原始类型。
- 变量类型必须精确为密封类,例如
Shape s = ...;,而不是Object s或Serializable s - 泛型中若用到密封类,注意类型擦除影响:方法签名里写
void handle(Shape s)才能触发穷尽检查,写成<T extends Shape> void handle(T t)就不行 - IDE 和 javac 17+ 才支持该检查;低版本 JDK 编译会直接忽略
sealed关键字
模块化环境下,permits 类必须可访问且未被封装
如果密封类在模块 A,子类在模块 B,光写 permits B.SubClass 不够——模块 A 必须导出包含该密封类的包,模块 B 必须对模块 A 有 requires,且子类所在包不能被模块 A 的 opens 或 exports 排除。
性能影响几乎为零,但兼容性风险高:JDK 17 默认开启强封装,未显式配置的模块间类加载会失败,错误信息通常是 class XXX is in unnamed module, but module Y does not read it 或更隐晦的 NoClassDefFoundError。
- 模块 A 的
module-info.java至少要写:exports com.example.shape; - 模块 B 的
module-info.java要写:requires A; - 若子类含反射操作(如
getDeclaredMethods()),还需在模块 A 中加opens com.example.shape to B;










