
sealed class 是 Kotlin 里一种用于限制继承层级的特殊类。
和普通类不同,它定义的是一组封闭类型,也就是说,它的直接子类在编译期就是确定的。
正因为如此,sealed class 很适合表达"只能有这些情况"的模型,比如状态、结果、有限分支等。
直接子类指的是直接继承父类的类;间接子类则是通过多层继承关系延伸出来的类。在 Kotlin 中,
sealed class的所有直接子类在编译期都是已知的。也就是说你不能在定义它的模块和包之外随意新增子类,因此可以获得更强的类型安全,并支持穷尽式when检查。
核心特性
sealed class 最重要的价值,在于它能把一个类型层级控制在一个有限、可预测的范围内(从这个角度看,和 enum class 类似):
- 封闭层级 :
sealed class的直接子类必须定义在同一个包中,因此继承树不会在别处被悄悄扩展。 - 穷尽
when:编译器知道它有哪些直接子类,因此在when表达式里会强制你处理所有分支;漏掉任何一种情况,代码就无法通过编译。 - 子类形式灵活 :子类既可以是普通类,也可以是
data class,还可以是object声明。
什么时候用
当你需要建模"有限集合"时,sealed class 往往是非常自然的选择。常见场景包括:
- 有限状态建模:比如网络/数据库请求状态(loading、success、error)、用户输入事件(click、scroll、request),或者 UI 状态(loading、fetched、error)。
- 统一包装不同数据:你可以把不同类型的数据放进同一个受限层级中,并以类型安全的方式分发和处理。
例如,可以定义一个 Result 类型来表示响应的不同状态:
kotlin
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val errorMessage: String) : Result()
data object Loading : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("Error: ${result.errorMessage}")
Result.Loading -> println("Loading...")
}
}
普通类可以在任何位置继续派生新子类,而 sealed class 的所有子类都被限制在同一个包内,因此整个层级是受控且有限的。
进阶:Class 和 Interface
sealed class 和 sealed interface 都是为了表达"受限层级",也就是子类型只能来自一组固定的候选项。但两者的定位和灵活性并不相同。
- Sealed Class :用来定义一组封闭的子类。子类必须继承这个
sealed class,并定义在同一个包中,因此编译器可以做穷尽检查。 - Sealed Interface:从 Kotlin 1.5 开始引入。它约束的是"实现者集合",而不是"子类集合",所以在跨多个类定义契约时更灵活,尤其适合需要多重继承的场景。
继承方式
sealed class 采用单继承,每个子类都只能扩展这个 sealed class,因此它更适合表达严格的层级关系。
sealed interface 则支持多重继承。一个类可以同时实现多个 sealed interface,所以它更适合表达多个能力组合而成、但实现集合仍然受限的模型。
灵活性与典型用途
sealed class 更适合那种类型层级必须被严格约束的场景,例如状态机中的有限状态。
sealed interface 更适合在多个类之间定义一组可复用的行为或属性,同时又要把实现者数量控制在一个封闭范围内。
Sealed Class 示例
下面这个例子展示了如何用 sealed class 表示一组封闭的支付方式:
kotlin
sealed class PaymentMethod {
object Cash : PaymentMethod()
object CreditCard : PaymentMethod()
data class PayPal(val email: String) : PaymentMethod()
}
Sealed Interface 示例
下面这个例子展示了如何用 sealed interface 表达两个能力:Clickable 和 Draggable,并让 Button 同时实现它们:
kotlin
sealed interface Clickable {
fun onClick()
}
sealed interface Draggable {
fun onDrag()
}
class Button : Clickable, Draggable {
override fun onClick() { println("Button clicked") }
override fun onDrag() { println("Button dragged") }
}
限制放宽
Sealed Interface Freedom 的 KEEP 提案希望放宽 Kotlin 1.5.0 中对 sealed class 和 sealed interface 的一些限制。
在 Kotlin 1.4.30 里,这两者的所有实现都必须定义在同一个文件中。提案希望允许实现分散在同一模块下的多个文件里,以提升大型项目中的可扩展性和模块化能力。
这种调整保留了 sealed interface 原有的优点,比如编译期的穷尽式 when 检查,同时也让代码组织方式更加灵活。为了保持安全性和可预测性,所有实现仍然必须属于同一个模块。提案的核心目标,是在不削弱类型安全的前提下改善开发体验。
同一编译单元、同一包中的其他类或接口,可以实现或扩展这个
sealed interface或sealed class。这样会形成一组在编译期就已知、且不能再继续向外扩展的直接子类。它们既可以是顶层声明,也可以嵌套在同包的具名类、具名接口或具名对象中。
小结
sealed class 适合表达严格、单继承的封闭层级;sealed interface 则提供了多重继承带来的灵活性,更适合描述一组封闭实现者所遵循的契约。两者都能让 when 分支做到穷尽,从而提升代码的安全性和可预测性。
开发者甚至可以这么认为------sealed 的存在就是为了更好的使用 when,让使用者能够预测和处理各种情况!