前置知识: Kotlin

密封类与代数数据类型

00:00
3 min Intermediate 2026/6/14

密封类与when穷举

概述

密封类(Sealed Class)是 Kotlin 中一种特殊的类层次结构,它限制子类只能在同一个文件(或同一个模块,Kotlin 1.5+)中定义。这个限制带来了一个重要的好处:编译器知道所有可能的子类,因此在使用 when 表达式时可以检查是否穷举了所有分支,如果遗漏了某个分支,编译器会报错。

密封类与代数数据类型(Algebraic Data Type,ADT)密切相关。代数数据类型是函数式编程中的概念,通过组合已有类型来定义新类型。密封类就是 Kotlin 对代数数据类型的实现,让你用类型系统精确地描述业务领域。

基础概念

  • sealed class:用 sealed 关键字标记的类,子类数量有限且已知
  • sealed interface:Kotlin 1.5 引入,与密封类类似但是接口
  • when 穷举:对密封类使用 when 时,必须覆盖所有子类,否则编译错误
  • 代数数据类型通过”和类型”(Sum Type,即密封类)和”积类型”(Product Type,即数据类)组合数据
  • 类型Sum Type:一个只能是类型中的一种,如 Result 要么是 Success 要么是 Failure

快速上手

// 定义密封接口,表示操作结果
sealed interface Result<out T> {
    data class Success<T>(val value: T) : Result<T>
    data class Failure(val error: String) : Result<Nothing>
}

// 使用 when 处理所有分支
fun handle(result: Result<Int>) = when (result) {
    is Result.Success -> println("成功: ${result.value}")
    is Result.Failure -> println("失败: ${result.error}")
    // 编译器确保穷举所有分支,不需要 else
}

详细用法

密封类与数据类组合

// 用密封类建模支付方式
sealed class PaymentMethod {
    // 每个子类携带不同的数据
    data class CreditCard(
        val number: String,
        val expiryMonth: Int,
        val expiryYear: Int
    ) : PaymentMethod()

    data class WeChatPay(
        val openId: String
    ) : PaymentMethod()

    data class BankTransfer(
        val accountNumber: String,
        val bankName: String
    ) : PaymentMethod()

    // 子类可以不带数据(object)
    object Cash : PaymentMethod()
}

// 处理不同支付方式
fun processPayment(method: PaymentMethod): String = when (method) {
    is PaymentMethod.CreditCard -> "信用卡支付: 尾号${method.number.takeLast(4)}"
    is PaymentMethod.WeChatPay -> "微信支付: ${method.openId}"
    is PaymentMethod.BankTransfer -> "银行转账: ${method.bankName}"
    PaymentMethod.Cash -> "现金支付"
}

fun main() {
    val methods = listOf(
        PaymentMethod.CreditCard("1234567890123456", 12, 2025),
        PaymentMethod.WeChatPay("user_123"),
        PaymentMethod.BankTransfer("6222000000", "工商银行"),
        PaymentMethod.Cash
    )
    methods.forEach { println(processPayment(it)) }
}

密封接口

Kotlin 1.5 引入了密封接口允许更灵活的层次结构

// 密封接口可以跨文件定义实现(同一模块内)
sealed interface Shape {
    val area: Double
}

sealed interface ColoredShape : Shape {
    val color: String
}

data class Circle(val radius: Double, override val color: String) : ColoredShape {
    override val area: Double = Math.PI * radius * radius
}

data class Rectangle(
    val width: Double,
    val height: Double,
    override val color: String
) : ColoredShape {
    override val area: Double = width * height
}

data class Triangle(
    val base: Double,
    val height: Double,
    override val color: String
) : ColoredShape {
    override val area: Double = 0.5 * base * height
}

fun describeShape(shape: Shape): String = when (shape) {
    is Circle -> "圆形,半径${shape.radius},面积${shape.area}"
    is Rectangle -> "矩形,${shape.width}x${shape.height},面积${shape.area}"
    is Triangle -> "三角形,底${shape.base}高${shape.height},面积${shape.area}"
}

嵌套密封类

sealed class UiState {
    // 加载中状态
    object Loading : UiState()

    // 成功状态,携带数据
    data class Success(val data: List<String>) : UiState()

    // 错误状态,可以进一步分类
    sealed class Error(val message: String) : UiState() {
        // 网络错误
        data class NetworkError(override val message: String) : Error(message)
        // 服务器错误
        data class ServerError(val code: Int, override val message: String) : Error(message)
        // 未知错误
        data class UnknownError(override val message: String) : Error(message)
    }
}

fun render(state: UiState): String = when (state) {
    is UiState.Loading -> "加载中..."
    is UiState.Success -> "数据: ${state.data}"
    is UiState.Error.NetworkError -> "网络错误: ${state.message}"
    is UiState.Error.ServerError -> "服务器错误(${state.code}): ${state.message}"
    is UiState.Error.UnknownError -> "未知错误: ${state.message}"
}

密封类与泛型

// 用密封类实现类型安全的异步结果
sealed class AsyncResult<out T> {
    // 未开始
    object Idle : AsyncResult<Nothing>()
    // 加载中
    object Loading : AsyncResult<Nothing>()
    // 成功
    data class Success<T>(val value: T) : AsyncResult<T>()
    // 失败
    data class Error(val exception: Throwable) : AsyncResult<Nothing>()
}

// 使用
fun <T> AsyncResult<T>.getOrElse(default: T): T = when (this) {
    is AsyncResult.Success -> value
    else -> default
}

fun <T> AsyncResult<T>.onSuccess(action: (T) -> Unit): AsyncResult<T> {
    if (this is AsyncResult.Success) action(value)
    return this
}

fun <T> AsyncResult<T>.onError(action: (Throwable) -> Unit): AsyncResult<T> {
    if (this is AsyncResult.Error) action(exception)
    return this
}

常见场景

网络请求状态管理

sealed class Resource<out T> {
    data class Success<T>(val data: T) : Resource<T>()
    data class Error(val message: String, val code: Int? = null) : Resource<Nothing>()
    object Loading : Resource<Nothing>()
}

class UserViewModel : ViewModel() {
    private val _userState = MutableStateFlow<Resource<User>>(Resource.Loading)
    val userState: StateFlow<Resource<User>> = _userState

    fun loadUser(id: String) {
        viewModelScope.launch {
            _userState.value = Resource.Loading
            try {
                val user = repository.getUser(id)
                _userState.value = Resource.Success(user)
            } catch (e: Exception) {
                _userState.value = Resource.Error(e.message ?: "未知错误")
            }
        }
    }
}

// 在 UI 中处理
@Composable
fun UserScreen(state: Resource<User>) {
    when (state) {
        is Resource.Loading -> CircularProgressIndicator()
        is Resource.Success -> Text("用户: ${state.data.name}")
        is Resource.Error -> Text("错误: ${state.message}")
    }
}

权限系统建模

sealed class Permission {
    sealed class Read : Permission() {
        object OwnData : Read()
        object AllData : Read()
    }
    sealed class Write : Permission() {
        object OwnData : Write()
        object AllData : Write()
    }
    object Admin : Permission()
}

fun checkAccess(userRole: String, permission: Permission): Boolean =
    when (permission) {
        Permission.Read.OwnData -> true  // 所有用户可读自己的数据
        Permission.Read.AllData -> userRole == "admin" || userRole == "manager"
        Permission.Write.OwnData -> userRole != "guest"
        Permission.Write.AllData -> userRole == "admin"
        Permission.Admin -> userRole == "admin"
    }

表达式求值器

// 用密封类建模数学表达式(经典的代数数据类型用法)
sealed class Expr {
    data class Num(val value: Int) : Expr()
    data class Add(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): Int = when (expr) {
    is Expr.Num -> expr.value
    is Expr.Add -> eval(expr.left) + eval(expr.right)
    is Expr.Mul -> eval(expr.left) * eval(expr.right)
    is Expr.Neg -> -eval(expr.expr)
}

fun main() {
    // 表达式: (2 + 3) * -4
    val expr = Expr.Mul(
        Expr.Add(Expr.Num(2), Expr.Num(3)),
        Expr.Neg(Expr.Num(4))
    )
    println(eval(expr))  // -20
}

注意事项

  • 子类必须在同一模块:Kotlin 1.5 之前要求同一文件,1.5 之后放宽到同一模块
  • when 必须穷举:如果作为表达式使用,必须覆盖所有子类;如果作为语句使用,建议覆盖所有子类编译器警告
  • 密封类是抽象的:不能直接实例化密封类本身,只能实例化它的子类
  • 构造器限制密封类构造器默认是 protected 的,不能声明public
  • 添加新子类会破坏现有代码:如果添加了新的子类,所有 when 表达式都需要更新,这既是优(不会遗漏)也是缺(需要修改的地方

进阶用法

与序列化配合

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
sealed class Message {
    abstract val id: String

    @Serializable
    @SerialName("text")
    data class Text(override val id: String, val content: String) : Message()

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

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

fun main() {
    val messages: List<Message> = listOf(
        Message.Text("1", "你好"),
        Message.Image("2", "photo.jpg", 800, 600),
        Message.System("3", "user_joined")
    )

    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(messages)
    // 序列化时会自动添加 type 鉴别器字段
    println(jsonString)

    // 反序列化时自动还原为正确的子类
    val decoded = json.decodeFromString<List<Message>>(jsonString)
}

递归密封类

sealed class JsonValue {
    data class JsonString(val value: String) : JsonValue()
    data class JsonNumber(val value: Double) : JsonValue()
    data class JsonBoolean(val value: Boolean) : JsonValue()
    object JsonNull : JsonValue()
    data class JsonArray(val elements: List<JsonValue>) : JsonValue()
    data class JsonObject(val entries: Map<String, JsonValue>) : JsonValue()
}

// 递归打印 JSON
fun prettyPrint(value: JsonValue, indent: Int = 0): String = when (value) {
    is JsonValue.JsonString -> "\"${value.value}\""
    is JsonValue.JsonNumber -> value.value.toString()
    is JsonValue.JsonBoolean -> value.value.toString()
    JsonValue.JsonNull -> "null"
    is JsonValue.JsonArray -> {
        val items = value.elements.joinToString(", ") { prettyPrint(it, indent + 2) }
        "[$items]"
    }
    is JsonValue.JsonObject -> {
        val entries = value.entries.entries.joinToString(", ") { (k, v) ->
            "\"$k\": ${prettyPrint(v, indent + 2)}"
        }
        "{$entries}"
    }
}

知识检测

学习进度

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

学习推荐

专注模式