密封类用于限制继承关系 ,即一个类的所有子类都必须在同一个文件 中定义。它本质上是枚举的升级版------枚举用于固定的一组对象 ,密封类用于固定的一组类型。
核心特点
- 子类受限:所有子类在编译时已知,只能出现在同一文件中;
- 可以有多个实例:相比枚举(每个常量只有一个实例),密封类的子类可以有多个对象;
- 支持状态携带:不同子类可携带不同类型的数据;
基本语法
kotlin
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
主要使用场景
1. 状态机表示(最常见)
kotlin
sealed class NetworkState {
object Idle
object Loading
data class Success(val data: String)
data class Error(val code: Int)
}
2. UI 事件/视图状态
kotlin
sealed class LoginEvent {
data class OnEmailChange(val email: String)
data class OnPasswordChange(val pwd: String)
object OnSubmitClick
}
与 when 表达式的完美配合
kotlin
fun handleState(state: Result) {
when (state) {
is Result.Success -> println(state.data)
is Result.Error -> println(state.message)
Result.Loading -> println("加载中")
// 无需 else 分支,编译器知道所有可能情况
}
}
与其他类型的对比
| 特性 | 密封类 | 枚举 | 普通抽象类 |
|---|---|---|---|
| 子类型数量 | 固定,编译时已知 | 固定 | 无限制 |
| 单例/多实例 | 两者都支持 | 仅单例 | 两者都支持 |
| 携带状态 | ✅ 不同子类可不同 | ❌ 所有常量相同结构 | ✅ |
| 模式匹配 | ✅ 完整检查 | ⚠️ 有限 | ❌ |
实例:加载用户信息
kotlin
// 1. 定义密封类表示加载状态
sealed class LoadState<out T> {
object Idle : LoadState<Nothing>()
object Loading : LoadState<Nothing>()
data class Success<T>(val data: T) : LoadState<T>()
data class Error(val message: String) : LoadState<Nothing>()
}
// 2. 模拟数据仓库
class UserRepository {
fun fetchUser(userId: String): LoadState<User> {
return when {
userId.isEmpty() -> LoadState.Error("用户ID不能为空")
userId == "404" -> LoadState.Error("用户不存在")
else -> LoadState.Success(User(userId, "张三", 25))
}
}
}
data class User(val id: String, val name: String, val age: Int)
// 3. UI 层处理状态
class UserViewModel {
private val repo = UserRepository()
fun loadUser(userId: String) {
var state: LoadState<User> = LoadState.Loading
println("状态: 加载中...")
state = repo.fetchUser(userId)
// 4. 使用 when 表达式处理所有情况(无需 else)
when (state) {
is LoadState.Idle -> println("状态: 空闲")
is LoadState.Loading -> println("状态: 加载中")
is LoadState.Success -> {
println("状态: 成功")
println("用户: ${state.data.name}, ${state.data.age}岁")
}
is LoadState.Error -> println("状态: 失败 - ${state.message}")
}
}
}
// 5. 测试
fun main() {
val vm = UserViewModel()
println("=== 测试1: 正常加载 ===")
vm.loadUser("123")
println("\n=== 测试2: 空ID ===")
vm.loadUser("")
println("\n=== 测试3: 不存在的用户 ===")
vm.loadUser("404")
}
注意事项
- 密封类本身是抽象的,不能直接实例化
- Kotlin 1.5+ 允许子类定义在不同文件的同一编译单元内,但通常还是放同一文件更清晰
- 密封类可以继承接口,也可以被继承(仅限子类)
简单总结:当想表达 "某个值只能是几种类型之一,且每种类型可以携带不同数据" 时,就用密封类。