密封类(sealed class)与switch表达式(switch expression)结合使用时,可以轻松实现编译期的穷尽性检查——编译器会强制要求你处理所有可能的子类型,即便没有default分支,也能保障代码的类型安全。不过,使用这一特性需要满足以下前提:父类型必须用sealed关键字声明,并通过permits明确列出允许的子类型;所有子类都需位于同一模块内,且声明为final、sealed或non-sealed;switch表达式必须覆盖全部子类以及null;同时,when子句并不能替代case完成类型覆盖。

简单来讲,就是让编译器在编译阶段主动帮你确认是否已处理所有可能的子类型——无需default分支,也能保证逻辑完整性与类型安全。听起来很方便?但具体规则需要仔细掌握。
密封类的定义必须完整且封闭
这是穷尽性检查的基础。父类型(类或接口)必须使用sealed关键字进行声明,并通过permits明确列出所有允许的直接子类型。
- 每一个子类型都必须存在,并且与父类型处于同一模块中
- 每个子类型必须显式声明为
final、sealed或non-sealed - 运行时不允许出现
permits列表之外的子类——否则switch表达式无法穷尽,编译时直接报错,没有任何商量的余地
Switch 表达式要覆盖全部子类型
switch的目标表达式类型必须是密封父类型;每个case直接写出子类名并绑定变量,Java会自动完成类型检查和字段解构。
case Circle c ->:这里的c是已经强转且可直接使用的Circle实例case Rectangle r ->:r是Rectangle类型,可以调用它的方法或访问它的字段- 所有
permits列出的子类都必须出现在case中,缺少任何一个就会编译报错——这是硬性约束
Null 必须单独处理
模式匹配的switch不会忽略null,也不会默认转到default。即使你的输入理论上不可能是空值,也需要显式编写case null。
case null -> throw new IllegalArgumentException("shape must not be null")- 也可以合并写成:
case null, default -> ...,但default在密封类场景下通常是冗余的 - 如果不写
null分支,编译器会直接拒绝——强制你正视空值边界
避免守卫条件破坏穷尽性认知
when子句用于补充判断条件,但它不会影响编译期的穷尽性检查——编译器只关心类型是否被全覆盖。
case String s when s.length() > 5 ->与case String s ->同时存在时,编译器仍认为String已被覆盖- 不能依靠
when来替代case:例如只写case String s when s.startsWith("A"),其他String就相当于被遗漏了 - 复杂的业务逻辑建议提前提取出来,保持
case主干清晰、易于验证
总的来说,密封类搭配switch表达式是Java类型安全方面的一大进步,但相关规则必须严格遵循——从父类声明到子类覆盖,再到null处理以及守卫条件,每个细节都影响着编译器的穷尽性校验。弄懂这些要点,才能真正用好这一特性。
