一杯美式讲完 Sealed Class

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 classsealed 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 表达两个能力:ClickableDraggable,并让 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 classsealed interface 的一些限制。

在 Kotlin 1.4.30 里,这两者的所有实现都必须定义在同一个文件中。提案希望允许实现分散在同一模块下的多个文件里,以提升大型项目中的可扩展性和模块化能力。

这种调整保留了 sealed interface 原有的优点,比如编译期的穷尽式 when 检查,同时也让代码组织方式更加灵活。为了保持安全性和可预测性,所有实现仍然必须属于同一个模块。提案的核心目标,是在不削弱类型安全的前提下改善开发体验。

同一编译单元、同一包中的其他类或接口,可以实现或扩展这个 sealed interfacesealed class。这样会形成一组在编译期就已知、且不能再继续向外扩展的直接子类。它们既可以是顶层声明,也可以嵌套在同包的具名类、具名接口或具名对象中。

小结

sealed class 适合表达严格、单继承的封闭层级;sealed interface 则提供了多重继承带来的灵活性,更适合描述一组封闭实现者所遵循的契约。两者都能让 when 分支做到穷尽,从而提升代码的安全性和可预测性。

开发者甚至可以这么认为------sealed 的存在就是为了更好的使用 when,让使用者能够预测和处理各种情况!

相关推荐
冬奇Lab13 小时前
PowerManagerService(下):Doze模式与电池优化
android·源码阅读
砖厂小工14 小时前
Compose 中函数引用 vs Lambda:到底该用哪个?
android
Kapaseker1 天前
详解 Compose background 的重组陷阱
android·kotlin
黄林晴1 天前
Kotlin 2.3.20-RC2 来了!JPA 开发者狂喜,6 大更新一文速览
android·kotlin
kymjs张涛2 天前
OpenClaw 学习小组:初识
android·linux·人工智能
糖猫猫cc2 天前
Kite:填充处理器
kotlin·orm·kite
范特西林2 天前
实战演练——从零实现一个高性能 Binder 服务
android
范特西林2 天前
代码的生成:AIDL 编译器与 Parcel 的序列化艺术
android
范特西林2 天前
深入内核:Binder 驱动的内存管理与事务调度
android