Kotlin 密封类 笔记

2 阅读3分钟

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 表达式可以实现完整的模式匹配,避免遗漏情况,提高代码的健壮性。