sealed类不能被任意类继承,因其核心作用是限制继承链,仅允许声明时显式指定的子类继承,从而保障类型安全与编译期穷尽性检查。

sealed 类为什么不能被任意类继承
密封类的核心作用是明确限制继承链——它只允许在声明时就指定的几个子类去继承,其他任何类都无法扩展它。这和 final 类不同:final 是彻底禁止继承,而 sealed 是“有名单的开放”。Kotlin 1.5+ 和 Java 15+(预览)都支持该特性,但语义和语法细节差异较大,实际用错常导致编译失败。
Kotlin 中 sealed class 的正确声明方式
Kotlin 要求所有直接子类必须与 sealed class 在同一个文件中(除非是 sealed interface,但那是 Kotlin 1.9+ 的新特性)。常见错误是把子类放到另一个文件,或忘了加 constructor() 修饰符(比如写成 class Sub : SealedBase 却没加 override 或构造参数匹配)。
实操建议:
-
sealed class本身必须是open(Kotlin 默认不 open,但 sealed 隐式 open,无需显式写) - 所有直接子类必须是非 private 的,且不能是
external或inner(除非是companion object内部的嵌套类) - 推荐用
object声明无状态的分支(如object Loading : UiState()),用class声明带数据的分支(如data class Success(val data: String) : UiState()) - 如果需要跨文件使用子类,改用
sealed interface+public实现类(Kotlin 1.9+),但要注意 JVM 字节码兼容性
Java 中 sealed class 的模块化约束
Java 的 sealed 类强制要求用 permits 明确列出允许继承的子类,且这些子类必须与父类在同一个模块中(或未命名模块)。最常踩的坑是:子类用了 public 但没在 permits 里声明,或者子类和父类不在同一模块却没导出包。
实操建议:
-
sealed class Shape permits Circle, Rectangle——Circle和Rectangle必须已存在、非final、且声明为non-sealed或final或sealed - 若子类是
non-sealed,它自己可以被进一步继承;若是final,则彻底终止继承链 - 模块
module-info.java中需确保子类所在包已exports,否则编译器报错class is not visible - Java 的 sealed 仅支持直接子类,不支持间接继承校验(即 A sealed → B extends A → C extends B 不受 A 约束)
sealed 类在 when 表达式中的穷尽性检查
这是 sealed 类最关键的收益点:Kotlin 编译器能静态确认 when 是否覆盖了所有可能分支,Java 则依赖 switch 的 sealed 支持(Java 21+ 正式版才稳定)。一旦漏写某个子类分支,Kotlin 直接报错 Exhaustive when expected,Java 则可能触发 Missing default label 警告(取决于编译选项)。
注意点:
- Kotlin 中,只要子类都在同一文件,
when (state) { is Loading -> ... }就能触发穷尽检查;若用了sealed interface且子类跨文件,需确保它们都被public且模块可见 - Java 的
switch要启用穷尽性,必须用sealed类型作为 selector,并且所有permits子类都出现在case中,或有一个default分支(否则编译失败) - 别在 sealed 类里留空的
else分支——那会绕过类型安全,失去密封意义
真正容易被忽略的是模块边界和文件位置约束:Kotlin 看文件,Java 看模块+permits+可见性,两者都不靠运行时检查,全靠编译期验证。一旦路径或模块配置偏移一格,错误信息往往不指向根本原因。

















