前置知识: Kotlin

密封类与密封接口

00:00
3 min Advanced 2026/6/14

Kotlin密封类与密封接口详解:受限继承与穷举检查。

概述

密封类(sealed class)和密封接口(sealed interface)限制继承范围,所有子类必须在同一文件或同一包中声明。编译器可以穷举所有子类型,配合 when 表达式实现类型安全的分支处理,是建模有限状态和代数数据类型的利器。

基础概念

密封类 vs 枚举

特性密封类枚举
实例数量每个子类可有多个实例每个值只有一个实例
状态携带子类可携带不同类型的数据所有共享相同属性
继承限制限定在同一文件/不可继承
类型检查when 穷举检查when 穷举检查

密封类的特点

  • 子类必须在同一文件声明(Kotlin 1.5+ 允许同一中不同文件
  • 子类可以是 data class、object 或普通 class
  • 密封类本身是抽象的,不能直接实例化
  • 配合 when 表达式编译器检查是否穷举所有分支

快速上手

密封类基本用法

// 定义密封类表示网络请求结果
sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

// when 表达式穷举所有子类,无需 else 分支
fun handle(result: Result<Int>) = when (result) {
    is Result.Success -> println("成功: ${result.value}")
    is Result.Error -> println("错误: ${result.message}")
    Result.Loading -> println("加载中...")
    // 编译器保证覆盖所有情况,不需要 else
}

密封接口基本用法

// 密封接口:允许多继承
sealed interface Action {
    data class Click(val x: Int, val y: Int) : Action
    data class Swipe(val direction: Direction) : Action
    object Idle : Action
}

// 密封接口可以组合
sealed interface Drawable {
    fun draw()
}

sealed interface Clickable {
    fun onClick()
}

// 同时实现两个密封接口
data class Button(val label: String) : Drawable, Clickable {
    override fun draw() { /* 绘制按钮 */ }
    override fun onClick() { /* 处理点击 */ }
}

详细用法

嵌套密封类

// 嵌套密封类构建层级结构
sealed class UiState {
    object Loading : UiState()

    sealed class Content : UiState() {
        data class UserList(val users: List<User>) : Content()
        data class UserDetail(val user: User) : Content()
    }

    sealed class Error : UiState() {
        data class Network(val code: Int) : Error()
        data class Auth(val reason: String) : Error()
    }
}

// 分层处理
fun render(state: UiState) = when (state) {
    is UiState.Loading -> showLoading()
    is UiState.Content.UserList -> showUserList(state.users)
    is UiState.Content.UserDetail -> showUserDetail(state.user)
    is UiState.Error.Network -> showNetworkError(state.code)
    is UiState.Error.Auth -> showAuthError(state.reason)
}

密封类与递归结构

// 使用密封类建模表达式树(递归结构)
sealed class Expr {
    data class Const(val value: Double) : Expr()
    data class Sum(val left: Expr, val right: Expr) : Expr()
    data class Mul(val left: Expr, val right: Expr) : Expr()
    data class Neg(val expr: Expr) : Expr()
}

// 递归求值
fun eval(expr: Expr): Double = when (expr) {
    is Expr.Const -> expr.value
    is Expr.Sum -> eval(expr.left) + eval(expr.right)
    is Expr.Mul -> eval(expr.left) * eval(expr.right)
    is Expr.Neg -> -eval(expr.expr)
}

// 构建表达式
val expression = Expr.Sum(
    Expr.Const(1.0),
    Expr.Mul(Expr.Const(2.0), Expr.Const(3.0))
)
println(eval(expression)) // 7.0

密封类与模式匹配

// 配合智能转换进行模式匹配
sealed class PaymentMethod {
    data class CreditCard(val number: String, val expiry: String) : PaymentMethod()
    data class WeChatPay(val openId: String) : PaymentMethod()
    data class BankTransfer(val account: String, val bank: String) : PaymentMethod()
}

fun processPayment(method: PaymentMethod): String = when (method) {
    is PaymentMethod.CreditCard -> "信用卡: ${method.number.takeLast(4)}"
    is PaymentMethod.WeChatPay -> "微信: ${method.openId.take(8)}"
    is PaymentMethod.BankTransfer -> "银行: ${method.bank}"
}

// 带守卫条件的匹配(Kotlin 1.7+)
fun describe(expr: Expr): String = when {
    expr is Expr.Const && expr.value == 0.0 -> "零"
    expr is Expr.Const -> "常量 ${expr.value}"
    expr is Expr.Sum -> "加法"
    expr is Expr.Mul -> "乘法"
    expr is Expr.Neg -> "取反"
    else -> "未知"
}

常见场景

UI 状态建模

// 使用密封类建模屏幕状态
sealed class ScreenState<out T> {
    object Idle : ScreenState<Nothing>()
    object Loading : ScreenState<Nothing>()
    data class Success<T>(val data: T) : ScreenState<T>()
    data class Error(val message: String, val retry: (() -> Unit)? = null) : ScreenState<Nothing>()
}

// 在 ViewModel 中使用
class UserViewModel : ViewModel() {
    private val _state = MutableStateFlow<ScreenState<User>>(ScreenState.Idle)
    val state: StateFlow<ScreenState<User>> = _state.asStateFlow()

    fun loadUser(id: String) {
        viewModelScope.launch {
            _state.value = ScreenState.Loading
            _state.value = try {
                ScreenState.Success(repository.fetchUser(id))
            } catch (e: Exception) {
                ScreenState.Error(e.message ?: "加载失败") { loadUser(id) }
            }
        }
    }
}

导航事件建模

// 使用密封类定义导航事件
sealed class NavigationEvent {
    data class ToDetail(val userId: String) : NavigationEvent()
    data class ToSettings(val tab: String) : NavigationEvent()
    object Back : NavigationEvent()
    data class DeepLink(val uri: String) : NavigationEvent()
}

// 处理导航
fun navigate(event: NavigationEvent) = when (event) {
    is NavigationEvent.ToDetail -> navController.navigate("user/${event.userId}")
    is NavigationEvent.ToSettings -> navController.navigate("settings/${event.tab}")
    NavigationEvent.Back -> navController.popBackStack()
    is NavigationEvent.DeepLink -> navController.navigate(Uri.parse(event.uri))
}

注意事项

  • Kotlin 1.5 之前子类必须在同一文件中,1.5+ 允许同一中不同文件
  • 密封类构造器默认是 protected,可以改为 private 但不能是 public
  • when 表达式如果不穷举所有分支编译器会报错(除非添加 else)
  • 密封接口不能有构造参数,但可以有抽象属性方法
  • 密封类配合泛型注意型变,ResultResult子类型
  • 在 Android 中推荐使用密封类替代枚举表示 UI 状态

进阶用法

密封类与序列化

// 密封类配合 JSON 序列化(使用类鉴别器)
@Serializable
sealed class Message {
    @Serializable
    @SerialName("text")
    data class Text(val content: String) : Message()

    @Serializable
    @SerialName("image")
    data class Image(val url: String, val width: Int, val height: Int) : Message()

    @Serializable
    @SerialName("system")
    data class System(val action: String) : Message()
}

// 序列化和反序列化
val json = Json { ignoreUnknownKeys = true }
val message: Message = json.decodeFromString(jsonString)
val serialized: String = json.encodeToString(message)

密封类与 StateFlow 结合

// 使用密封类 + StateFlow 构建单向数据流
sealed class Wish {
    data class LoadItems(val category: String) : Wish()
    data class DeleteItem(val id: String) : Wish()
    object Refresh : Wish()
}

class ItemViewModel : ViewModel() {
    private val _state = MutableStateFlow<ScreenState<List<Item>>>(ScreenState.Idle)
    val state = _state.asStateFlow()

    fun accept(wish: Wish) {
        when (wish) {
            is Wish.LoadItems -> loadItems(wish.category)
            is Wish.DeleteItem -> deleteItem(wish.id)
            Wish.Refresh -> refresh()
        }
    }
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式