密封类用于禁止任意继承,强制所有子类型与之同文件定义以实现穷举验证;其子类只能是同文件的class或object,配合when可确保编译期穷尽检查。

密封类(Sealed Class)不是用来“实现继承”的,而是用来“禁止任意继承”的——它强制你显式声明所有合法子类型,从而让类型体系可穷举、可验证。
为什么 sealed class 不能被普通 extends 继承
在 Kotlin 中,sealed class 默认是 final 的:任何未在同一个文件中直接声明的子类都会编译失败。这不是语法限制,而是设计契约——所有子类型必须和密封类定义在同一个文件里(或嵌套在其中),编译器才能静态确认“就这些”。
- 错误现象:
Class 'MySub' is not allowed to inherit from sealed class 'Result' - 常见原因:把子类写在另一个文件、用了
open class中间层、或误用interface模拟多态 - 正确做法:子类必须是
class Sub : Result()或object Success : Result(),且与sealed class Result同文件
如何配合 when 表达式做穷尽检查
密封类的核心价值在 when:当所有分支都被覆盖,Kotlin 编译器能确保无遗漏;一旦新增子类,所有未更新的 when 会立刻报错(而不是运行时崩溃)。
- 必须写全分支,否则报错:
Non-exhaustive 'when' expression: the following cases are missing: Success, Error - 允许省略
else分支(前提是已覆盖全部子类型) - 如果用了
is类型检查而非when,就失去穷尽性保障——别这么做 - 示例:
fun handleResult(r: Result) = when (r) { is Result.Success -> r.data is Result.Error -> throw r.exception }
object 和 class 子类的选择依据
密封类下混用 object(单例)和 class(可构造)不是风格问题,而是语义约束:
- 状态无关、无需参数的场景(如加载中、完成、失败信号)→ 用
object Loading : Result() - 携带数据的场景(如成功返回值、错误详情)→ 用
class Success(val data: String) : Result() - 避免对
object子类使用构造函数调用(Success()会报错),也别给object加属性(除非是val常量) - 如果需要序列化,
object自动是单例,但class子类需手动加@Serializable
Java 互操作时密封类会退化成普通抽象类
Kotlin 的密封性在 JVM 字节码层面无法完全保留。Java 代码能看到的是一个抽象类 + 若干 public 子类,且 Java 可以随意继承它们——这会破坏原本的设计意图。
- 后果:Java 调用方可能写出未被
when覆盖的新子类实例,导致运行时MatchError - 缓解方式:在密封类上加
@JvmInline不可行(不支持),只能靠文档约束 + 封装模块边界(如只暴露 sealed class 的伴生对象工厂方法) - 更稳妥的做法:若模块要被 Java 大量调用,改用
enum class(仅限无状态)或自定义Result接口 + 私有实现 + 静态工厂
真正难的不是写对语法,而是在团队协作中守住“所有子类型必须同文件”这条线——IDE 有时会自动把新类生成到错误包路径,或者重构时不小心移走,这类问题不会立刻报错,但会让 when 的穷尽性失效。

















