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