要让编译器真正穷举校验所有业务分支,必须同时满足:密封类用 permits 明确封闭直接子类、switch 使用类型模式语法、JDK 版本与编译配置正确(Java 21+ 默认支持)、且严禁 default/else 分支。
要让编译器真正帮你“盯住”所有业务分支,关键不是单用密封类,也不是只写个 switch,而是把两者按规则咬合起来——子类范围必须封闭、匹配语法必须正确、编译环境必须就位。漏掉任一环,穷举校验就失效。
密封类必须显式封死所有直接子类
编译器能做穷举检查的前提,是它确切知道“总共可能有哪些类型”。这靠 permits 列表 保证:
- 密封类(interface 或 class)声明时必须带
permits A, B, C,且 A/B/C 必须是直接实现/继承者,不能是孙子类 - 每个被允许的子类必须明确声明为
final、sealed或non-sealed;若选non-sealed,后续可任意扩展,穷举即失效 - 所有子类需与密封父类在同一个模块下;跨包时,permits 中必须写全限定名(如
com.example.Payment.Success)
switch 必须用类型模式,不能退化为普通 case
只有启用模式匹配语法,编译器才会把每个 case 当作一个“已知子类型分支”来统计。写错格式等于主动放弃检查:
- ✅ 正确:
case Payment.Success s -> s.id()(带变量绑定的类型模式) - ✅ 正确:
case Payment.Failure(String msg) -> log(msg)(记录类解构模式,Java 21+) - ❌ 错误:
case SUCCESS:(只是枚举常量,不触发类型穷举) - ❌ 错误:
case var x when x instanceof Payment.Failure f:(混用 instanceof,绕过密封体系)
确保编译器开启并识别该特性
Java 17–20 需显式启用预览特性,Java 21+ 才默认可用:
- 用 JDK 17~20:编译命令加
--enable-preview,运行时也必须加,否则报IncompatibleClassChangeError - 用 JDK 21+:无需 flag,但需确认
javac -version输出 ≥ 21 - IDE(如 IntelliJ)需设置 Project SDK 和 Language Level 均为对应版本,否则提示“switch expression does not cover all possible values”却无法定位问题
拒绝 else 分支,用编译错误代替运行时兜底
加 default 或 else 看似保险,实则让编译器放弃校验——它会认为“你已主动处理未知情况”,不再提醒新增子类未覆盖:
- 正确做法:不写 else,让编译失败成为信号;一旦新增子类,所有 switch 处立刻报错,强制补全逻辑
- 若真有“理论上不可达但语法需兜底”的场景,可用
throw new AssertionError("unreachable")并加注释说明前提条件 - 避免在 switch 外使用
instanceof或getClass()判断,这些路径完全脱离编译期检查范围

















