密封类本身不提供编译期穷举检查,但配合 when/match/联合类型判别式等可实现全覆盖验证;其前提是子类同文件定义、不可扩展、且必须用表达式语法而非is链。

密封类(Sealed Class)体系本身不直接提供“编译器层面的完备性穷举检查”,但配合 when 表达式(Kotlin) 或 模式匹配(Scala 3、Rust enum + match、TypeScript discriminated union + exhaustive checking),可在编译期强制要求覆盖所有已知子类型,从而实现逻辑上的“穷举完备性检查”。
核心机制:编译器知道所有可能的子类型
密封类的关键约束是:所有直接子类必须与密封类定义在同一个文件中(Kotlin)或同一作用域内(如 Scala 的 sealed trait / Rust 的 enum)。这使得编译器能静态确定该类型的所有可能具体值——没有外部未知子类可逃逸,因此 when / match 的分支可以被验证是否“全覆盖”。
- Kotlin 中 sealed class + when:若未处理某个子类或缺少 else 分支(且类型非可空),编译报错
- Rust 中 enum + match:必须列出所有变体,或用 _ 占位;若未来新增变体,所有 match 处都会立即报错
- TypeScript 中联合类型 + discriminant field(如 type Shape = Circle | Square; interface Circle { kind: 'circle'; })配合 --exactOptionalPropertyTypes 和类型守卫或 switch,结合插件(如 exhaustive-switch)可补全检查
确保真正“完备”的三个前提
仅声明 sealed 不够,需配合语言特性和编码约定:
- 子类必须为 final / 不可继承(Kotlin 默认密封子类是 final;Rust enum 变体天然封闭;若用 abstract class + sealed,在 Kotlin 中需显式禁止进一步继承)
- when / match 必须是表达式上下文且返回类型统一(避免漏写分支后靠运行时默认行为兜底)
- 禁止使用 is-check + if 链替代 when:is 检查无法被编译器追踪覆盖度,会绕过完备性校验
常见陷阱与应对
看似用了密封类,却失去编译期保障的情况:
- 子类定义在其他文件(Kotlin)→ 违反密封规则,编译失败,但若误用 open class 模拟,则彻底丢失检查能力
- when 中用了 else 分支且逻辑隐含“不可能情况” → 编译通过,但削弱了穷举意义;应改用 throw 或 assert(false) 并加注释说明为何此处不可达
- 密封类包含 data class 子类,但又在 when 外部用 == 或 toString 判断 → 这些操作脱离模式匹配体系,无法享受完备性检查
进阶:与代数数据类型(ADT)和 exhaustiveness 插件协同
在不原生支持的环境(如 Java),可用枚举 + 访问者模式模拟,但无法获得编译器强制;更实用的是:
- 用 Kotlin + detekt 或 kotlin-compiler-plugins 实现自定义检查(如确保所有 sealed 子类都在 when 中出现)
- TypeScript 项目启用 typescript-exhaustive-check 或 ts-pattern 库,在编译时或 lint 阶段插入穷举校验
- Rust 中无需额外工具,match 的 exhaustiveness 是语言级保证,且 IDE(如 rust-analyzer)实时高亮缺失分支

















