Kotlin 密封类(Sealed Class)
密封类是 Kotlin 中一种特殊的类,用于表示受限的类层次结构。它定义了一组固定的子类,编译器知道所有可能的子类,这为类型安全提供了保证。
基本概念
1. 基本语法
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
2. 主要特点
- ✅ 所有子类必须在同一个文件中声明(Kotlin 1.1 之前需要嵌套在密封类内部)
- ✅ 密封类本身是抽象类,不能直接实例化
- ✅ 子类可以是数据类、对象、普通类、其他密封类
- ✅ 在
when表达式中使用时,编译器可以检查是否覆盖所有情况
核心优势
1. 类型安全的 when 表达式
fun handleResult(result: Result) = when (result) {
is Result.Success -> println("Success: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
Result.Loading -> println("Loading...")
// 不需要 else 分支,编译器知道所有可能情况
}
// 如果新增子类,编译器会报错提示需要处理新情况
2. 替代枚举类(Enum)的场景
// 枚举类 - 每个枚举常量是单例
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
// 密封类 - 每个子类可以有多个实例,携带不同数据
sealed class Operation {
data class Add(val value: Int) : Operation()
data class Multiply(val value: Int) : Operation()
object Reset : Operation()
}
val op1 = Operation.Add(5) // 新的实例
val op2 = Operation.Add(10) // 另一个实例
使用场景
1. API 响应处理
sealed class ApiResponse<out T> {
data class Success<T>(val data: T) : ApiResponse<T>()
data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()
object Loading : ApiResponse<Nothing>()
}
fun handleResponse(response: ApiResponse<String>) {
when (response) {
is ApiResponse.Success -> {
println("Data: ${response.data}")
// response.data 被智能转换为 String
}
is ApiResponse.Error -> {
println("Error ${response.code}: ${response.message}")
}
ApiResponse.Loading -> {
println("Loading...")
}
}
}
2. UI 状态管理
sealed class ViewState {
data class Content(val items: List<String>) : ViewState()
data class Error(val message: String) : ViewState()
object Loading : ViewState()
object Empty : ViewState()
}
class ViewModel {
private val _state = MutableStateFlow<ViewState>(ViewState.Loading)
val state = _state.asStateFlow()
fun loadData() {
_state.value = ViewState.Content(listOf("Item 1", "Item 2"))
}
}
// 在 UI 中
viewModel.state.collect { state ->
when (state) {
is ViewState.Content -> showContent(state.items)
is ViewState.Error -> showError(state.message)
ViewState.Loading -> showLoading()
ViewState.Empty -> showEmptyView()
}
}
3. 表达式树
sealed class Expr {
data class Number(val value: Int) : Expr()
data class Sum(val left: Expr, val right: Expr) : Expr()
data class Multiply(val left: Expr, val right: Expr) : Expr()
}
fun eval(expr: Expr): Int = when (expr) {
is Expr.Number -> expr.value
is Expr.Sum -> eval(expr.left) + eval(expr.right)
is Expr.Multiply -> eval(expr.left) * eval(expr.right)
}
val expression = Expr.Sum(Expr.Number(2), Expr.Multiply(Expr.Number(3), Expr.Number(4)))
println(eval(expression)) // 输出: 14
4. 导航/路由
sealed class Screen(val route: String) {
object Home : Screen("home")
object Profile : Screen("profile")
data class Details(val id: String) : Screen("details/$id")
data class Settings(val section: String) : Screen("settings?section=$section")
}
fun navigateTo(screen: Screen) {
when (screen) {
Screen.Home -> // 导航到首页
Screen.Profile -> // 导航到个人资料
is Screen.Details -> // 导航到详情页,带参数
is Screen.Settings -> // 导航到设置页
}
}
进阶用法
1. 密封接口
sealed interface Result<out T>
data class Success<T>(val data: T) : Result<T>
data class Failure(val error: Throwable) : Result<Nothing>
object Loading : Result<Nothing>
// 可以同时实现多个接口
sealed interface Response
sealed interface Errorable : Response
data class SuccessResponse(val data: Any) : Response
data class ErrorResponse(val message: String) : Response, Errorable
2. 嵌套密封类
sealed class Vehicle {
sealed class Car : Vehicle() {
data class Sedan(val seats: Int) : Car()
data class SUV(val offroad: Boolean) : Car()
}
sealed class Bike : Vehicle() {
object MountainBike : Bike()
object RoadBike : Bike()
}
}
3. 结合泛型
sealed class Resource<out T> {
data class Success<out T>(val data: T) : Resource<T>()
data class Error(val exception: Exception) : Resource<Nothing>()
object Loading : Resource<Nothing>()
}
fun <T> process(resource: Resource<T>) {
when (resource) {
is Resource.Success -> handleSuccess(resource.data)
is Resource.Error -> handleError(resource.exception)
Resource.Loading -> showLoading()
}
}
最佳实践
1. 位置限制
// 文件: Result.kt
sealed class Result
// 必须在同一个文件中
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
// 错误:在不同文件中声明子类
// class AnotherResult : Result() // 编译错误
2. 何时使用密封类 vs 枚举
// 使用枚举:有限的、固定的实例,没有额外数据
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
// 使用密封类:每个"类型"可能有多个实例,携带不同数据
sealed class Command {
data class Move(val distance: Int) : Command()
data class Turn(val angle: Double) : Command()
object Stop : Command()
}
3. 模式匹配的完整性检查
sealed class Shape {
data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
}
fun area(shape: Shape): Double = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.width * shape.height
// 如果忘记处理某个子类,编译器会警告
}
// 使用 exhaustive when(Kotlin 1.7+)
val area = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.width * shape.height
}.exhaustive
实际应用示例
Redux-like 状态管理
sealed class Action {
data class AddTodo(val text: String) : Action()
data class ToggleTodo(val id: Int) : Action()
object ClearCompleted : Action()
data class SetVisibilityFilter(val filter: Filter) : Action()
}
sealed class Filter {
object ShowAll : Filter()
object ShowActive : Filter()
object ShowCompleted : Filter()
}
fun reducer(state: State, action: Action): State = when (action) {
is Action.AddTodo -> state.copy(todos = state.todos + Todo(action.text))
is Action.ToggleTodo -> state.copy(
todos = state.todos.map {
if (it.id == action.id) it.copy(completed = !it.completed)
else it
}
)
Action.ClearCompleted -> state.copy(
todos = state.todos.filter { !it.completed }
)
is Action.SetVisibilityFilter -> state.copy(filter = action.filter)
}
总结:Kotlin 密封类通过限制继承层次,提供了编译时的类型安全保证。它在状态管理、API 响应处理、表达式解析等场景中特别有用,结合 when 表达式可以实现完整的模式匹配,避免遗漏情况,提高代码的健壮性。