1. 简介

Kotlin 语言借鉴了不少函数式编程语言的设计理念,旨在帮助开发者写出更安全、更具可读性的代码。Sealed 类(Sealed Classes) 就是其中之一,它为类型系统提供了更强的约束能力,尤其适用于需要严格控制继承结构的场景。

2. 什么是 Sealed Class?

Sealed Class 的核心作用是:限定类的继承体系,禁止在指定范围之外新增子类。

换句话说,一旦你定义了一个 sealed class,所有可能的子类都必须在编译期就已知——这使得类型判断变得完全可预测和穷尽。

从 Kotlin 1.5 开始,seated class 的子类可以分布在同一个编译单元(module)中不同文件里,只要它们处于同一包名下即可 —— 这极大提升了使用灵活性。
🔗 参考:Kotlin 1.5 新特性 - 包级密封类

⚠️ 注意点:

  • Sealed class 是隐式 abstract 的,不能被实例化。
  • 虽然不能被外部随意继承,但它本身可以包含字段、方法,支持抽象方法和具体实现。
  • 子类必须显式继承该 sealed class,并且只能是其直接子类(不允许间接扩展)。

2.1 Sealed Interface

自 Kotlin 1.5 起,interface 也支持 sealed 修饰符。
🔗 官方文档:Sealed Interfaces

与 sealed class 类似,所有实现 sealed interface 的类/对象也必须在编译期可知。

✅ 相比 sealed class,seated interface 的一大优势是:支持多继承
因为 Kotlin 不允许多重继承类,但可以实现多个接口。因此当你需要组合多种状态或行为时,seated interface 更加灵活。

举个例子:

sealed interface Result
sealed interface Loading : Result
sealed interface Error : Result

data class Success(val data: String) : Result
object LoadingState : Loading
data class ErrorState(val msg: String) : Error, Result

这样你可以通过组合不同的 sealed interface 来构建复杂的类型系统。

3. 何时使用 Sealed Class?

Sealed class 最适合用于表示有限且互斥的状态集合,每个子类代表一种语义上不同的情况——类似于函数式语言中的 代数数据类型(Algebraic Data Types, ADT)
🔗 维基百科:Algebraic Data Type

典型应用场景包括:

  • ✅ 实现状态机(State Machine)
    🔗 比如 UI 加载、成功、失败三种状态
  • ✅ 结果封装(Result/Option 类型)
  • ✅ 单一来源的数据流处理(如网络请求响应)
  • ✅ 函数式风格的 Monadic 编程(比如 Either、Try)

❌ 不适用场景:

  • 当选项数量不确定或未来可能动态扩展时 → 应该用普通 class 或 open class
  • 所有变体只是数据差异而无行为区别 → 建议改用 枚举类(Enum Class)

💡 小贴士:如果你发现你在 when 表达式中频繁写 else -> throw IllegalStateException(),那很可能你应该考虑换成 sealed class —— 编译器会帮你检查是否遗漏分支!

4. 如何编写 Sealed Class

我们以 Java 8 中的 Optional<T> 为例来说明。虽然 Java 没有 sealed class,导致任何人都能继承 Optional,但在 Kotlin 中我们可以做得更好。

设想一个 Optional<V> 类型,只允许两种状态:有值(Some)或无值(None)。我们要确保没有人能创建第三种实现。

sealed class Optional<out V> {
    abstract fun isPresent(): Boolean
}

data class Some<out V>(val value: V) : Optional<V>() {
    override fun isPresent(): Boolean = true
}

class None<out V> : Optional<V>() {
    override fun isPresent(): Boolean = false
}

现在可以保证:任何 Optional<V> 实例,必然是 Some<V>None<V> 之一。

使用示例:

val result: Optional<String> = divide(1, 0)
println(result.isPresent()) // 输出 false

if (result is Some) {
    println(result.value) // 安全访问 value
}

这里的 divide 函数可返回 None 表示除零错误,或者 Some("0.5") 表示正常结果。

⚠️ 踩坑提醒:泛型协变 <out V> 很关键!否则 Optional<String> 无法赋值给 Optional<Any>,影响实用性。

5. 与 when 表达式配合使用

这是 sealed class 的杀手级特性:when 配合时,编译器能检测分支是否穷尽。

由于所有子类在编译期已知,Kotlin 可以确保你处理了所有可能的情况,无需写 else 分支。

继续上面的例子:

val message = when (result) {
    is Some -> "Answer: ${result.value}"
    is None -> "No result"
}
println(message)

如果漏掉任一分支,比如删掉 is None

'when' expression must be exhaustive, add necessary 'else' branch

编译直接报错!这种“失败在编译期”机制极大提升了代码健壮性。

✅ 优势总结:

  • 添加新子类后,所有未处理该类型的 when 表达式都会立即报错
  • 无需防御性 else throw
  • 提升重构安全性

6. 总结

Sealed class 是现代 Kotlin 开发中不可或缺的工具之一,特别适合建模封闭的、有限的状态体系

它带来的主要好处包括:

  • ✅ 类型安全:杜绝意外的子类扩展
  • ✅ 可维护性强:配合 when 实现编译期穷尽检查
  • ✅ 语义清晰:每个子类表达明确意图
  • ✅ 支持泛型与数据类,实用性强

合理使用 sealed class + sealed interface,可以让你的 API 设计更加严谨、易读、不易出错。

📌 示例代码已托管至 GitHub:
👉 https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop


原始标题:Sealed Classes in Kotlin