一杯美式讲完 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,让使用者能够预测和处理各种情况!

相关推荐
Jomurphys11 小时前
Compose 适配 - 通过 UiMediaScope 获取设备信息
android·compose
阿巴斯甜11 小时前
必看12
android
阿巴斯甜11 小时前
必看11
android
solo_9911 小时前
Perftto 使用命令添加标签
android
阿巴斯甜12 小时前
必看10
android
阿巴斯甜12 小时前
必看9
android
阿巴斯甜12 小时前
必看6
android
angerdream12 小时前
Android手把手编写儿童手机远程监控App之SQLite详解
android
阿巴斯甜12 小时前
必看5
android
雪铃儿13 小时前
Shorebird 之外,Flutter Android 热更新还有什么选择
android·前端