前置知识: KotlinKotlinKotlin

密封类与密封接口

89 minAdvanced2026/7/21

Kotlin 密封类与密封接口深度解析:受限继承、穷举检查、代数数据类型(ADT)、模式匹配、递归结构、when 表达式编译期检查的形式化定义、字节码实现与企业级工程实践。对标 MIT 6.005、Stanford CS193P、CMU 15-214 教学水准。

密封类与密封接口(Sealed Classes and Sealed Interfaces in Depth)

本文档对标 MIT 6.005 Software Construction、Stanford CS193P iOS Development、CMU 15-214 Software Engineering 等海外名校课程的教学水准,系统讲解 Kotlin 密封类(sealed class)与密封接口(sealed interface)的设计动机、形式化语义、穷举检查(exhaustiveness checking)原理、代数数据类型(Algebraic Data Type, ADT)建模、递归结构与模式匹配、JVM 字节码实现以及企业级工程实践。本文不假设读者具备 Scala、Rust 或 Haskell 的前置经验,所有概念均从”为什么需要受限继承”出发,逐层深入到编译器实现层面。完成本文学习后,读者将能够运用密封类型构建类型安全的领域模型、状态机与递归数据结构,并理解其在现代架构(MVI、MVVM、Event Sourcing)中的核心作用。

目录

  1. 学习目标
  2. 历史动机与发展脉络
  3. 形式化定义
  4. 理论推导与原理解析
  5. 代码示例
  6. 对比分析
  7. 常见陷阱与最佳实践
  8. 工程实践
  9. 案例研究
  10. 习题
  11. 参考文献
  12. 延伸阅读

1. 学习目标

本章节遵循 Bloom 教育目标分类学(Bloom’s Taxonomy)的六个认知层级,由低阶到高阶逐层递进。Bloom 分类学由教育心理学家 Benjamin Bloom 于 1956 年提出,2001 年由 Anderson 与 Krathwohl 修订,是国际教育界普遍采用的认知能力分级框架。

1.1 Remember(记忆)

完成本章节后,学习者应能够准确记忆以下知识点:

  • 复述 Kotlin 密封类(sealed class)的定义语法:sealed class ClassName,所有直接子类必须在同一文件(Kotlin 1.5 前要求)或同一包内(Kotlin 1.5+ 允许)声明。
  • 列举密封类的三类合法子类形态:data class(携带数据)、object(单例)、普通 class(可继承)。
  • 背诵密封接口(sealed interface)于 Kotlin 1.5 引入,允许类、其他密封接口、objectenum class 实现。
  • 记忆 when 表达式穷举检查的触发条件:作为表达式使用(有返回值)且未提供 else 分支时,编译器强制要求覆盖所有子类型。
  • 列举 Kotlin 1.0 至 2.0 期间密封类的关键演进:1.0 引入、1.1 支持子类在同文件不同位置、1.5 允许跨文件同包 + 引入密封接口、1.7 支持密封接口嵌套、1.9 与 K2 兼容、2.0 K2 全面优化穷举检查精度。
  • 复述密封类构造器的可见性规则:默认 protected,可显式声明 private,但不能为 publicinternal
  • 记忆密封类本身是抽象的(abstract),不能直接实例化,且不能声明为 open(隐式 abstract)。
  • 列举密封类与枚举的四大差异:实例数量(多实例 vs 单实例)、状态携带(异构 vs 同构)、继承层级(可嵌套 vs 扁平)、类型参数(支持泛型 vs 不支持)。

1.2 Understand(理解)

  • 用自己的语言解释”受限继承”(restricted inheritance)的含义:编译器在编译期就知道一个密封类的所有可能子类型,从而支持穷举检查。
  • 阐述密封类如何解决”开放式继承 + when 分支”的类型安全问题:开放继承下编译器无法保证 when 覆盖所有子类,新增子类时旧代码可能遗漏分支;密封类在新增子类时强制所有 when 表达式更新或编译失败。
  • 描述代数数据类型(Algebraic Data Type, ADT)的概念:由”和类型”(Sum Type)与”积类型”(Product Type)组合而成的数据类型,密封类对应和类型,data class 对应积类型。
  • 解释为什么密封类在 JVM 字节码层面使用 @Metadata 注解而非特殊字节码:JVM 不原生支持密封类,Kotlin 编译器将子类列表序列化到 @Metadata 注解中,供编译器跨模块检查。
  • 阐述密封接口相比密封类的两大优势:支持多继承(一个类可实现多个密封接口)与支持 enum class 实现(枚举值可作为密封接口的子类型)。
  • 解释”穷举检查”(exhaustiveness checking)的实现原理:编译器收集密封类的所有直接子类(含嵌套),与 when 分支的 is 检查对比,缺失即报错。
  • 描述智能转换(Smart Cast)在 when 分支中的作用:is Result.Success 后编译器自动将 result 细化为 Result.Success 类型,可直接访问 value 属性。
  • 解释为什么密封类配合 out 型变(covariance)能优雅表达”成功有值、失败无值”的场景:Result<Nothing>Result<T> 的子类型,可安全赋给 Result<T>

1.3 Apply(应用)

  • 在网络请求结果建模中使用 sealed class Result<out T>,覆盖 LoadingSuccess<T>Error 三态,并在 UI 层用 when 穷举处理。
  • 在 Android/iOS ViewModel 中使用密封类建模 UI 状态(IdleLoadingContentError),配合 StateFlow 实现单向数据流。
  • 使用嵌套密封类构建层级化的领域模型,如 sealed class UiState 嵌套 sealed class Contentsealed class Error,实现分层处理。
  • 使用密封类建模递归数据结构(如表达式树、JSON 树、AST 节点),用 when 递归求值或遍历。
  • 在 MVI 架构中使用密封类建模 Wish(用户意图)与 Effect(副作用),保证所有意图都有对应处理器。
  • 在事件溯源(Event Sourcing)中使用密封类建模领域事件,所有事件类型在编译期已知。
  • 使用 kotlinx.serialization 配合密封类,通过 @SerialName 类鉴别器实现多态 JSON 序列化。
  • 在 KMP 项目中使用密封接口定义跨平台共享的领域模型,各平台实现各自的 UI 处理。

1.4 Analyze(分析)

  • 反编译 Kotlin 密封类字节码,分析 @Metadata 注解中 d2 数组的存储结构:子类列表以 JVM 内部类名形式序列化。
  • 对比密封类与 Scala sealed trait、Rust enum、Swift enum、Haskell data 的穷举检查机制:四者均支持编译期穷举,但实现机制不同(注解 vs 关键字 vs 编译器内置)。
  • 分析嵌套密封类的穷举传播规则:when 处理外层密封类时,若分支只检查到 is Content(未深入子类),需要继续处理 Content 的子类,否则视为未穷举。
  • 解构 Kotlin 1.5 跨文件子类的实现:编译器扫描同包所有 .kt 文件,收集直接子类,然后注入 @Metadata
  • 分析密封接口支持多继承的语义价值:一个 data class 可同时实现 DrawableClickable 两个密封接口,在两个独立的 when 中分别处理。
  • 分析 Kotlin 2.0 K2 编译器对穷举检查的改进:K2 能识别更多边界条件(如 when 嵌套、is 后跟 !isnull 与非空分支组合),减少误报。
  • 对比 when (x) { is A -> ...; is B -> ...; else -> ... }(带 else)与 when (x) { is A -> ...; is B -> ... }(无 else)在密封类场景下的语义差异:前者放弃穷举检查,新增子类不会报错;后者强制穷举,新增子类时编译失败。

1.5 Evaluate(评价)

  • 评价 Kotlin 选择”包级受限继承”而非”模块级”或”文件级”的设计权衡:包级更灵活(支持多文件拆分),但跨模块时仍需 sealed 关键字显式声明子类位置。
  • 评价密封类构造器默认 protected 的设计:相比 private 提供了子类可调用的便利,相比 public 限制了外部实例化,是合理的折中。
  • 评估密封接口与密封类共存的设计:是否过度复杂?还是必要的灵活性补充?
  • 评价 Kotlin 不引入 Rust match 的”绑定模式”(binding patterns)的决策:Kotlin 的 is + 智能转换已覆盖 80% 场景,但嵌套解构(如 Some(Some(x)))确实不如 Rust 优雅。
  • 评估密封类在事件溯源架构中的适用性:相比 Java 的接口 + 实现类,密封类提供了编译期穷举保证,但牺牲了开放扩展性。
  • 评价密封类配合 when 表达式 vs if-else 链:前者在编译期检查穷举,后者无任何保证,应优先选择 when
  • 评估 Kotlin 密封类与 Java record + sealed(Java 17+)的差异:Java 的 sealed 关键字与 permits 子句更显式,但语法更冗长;Kotlin 更简洁但依赖 @Metadata 注解。

1.6 Create(创造)

  • 设计并实现一个完整的”表达式求值器”:用密封类建模算术表达式(常量、变量、加减乘除、函数调用),实现 eval(env: Map<String, Double>) 递归求值,并支持变量替换与简化。
  • 设计一个基于密封类的”状态机 DSL”:定义 StateEventTransition,用 when 穷举所有 state + event 组合,编译期保证完整性。
  • 实现一个”JSON 树”建模与遍历框架:用密封类表示 JsonObjectJsonArrayJsonStringJsonNumberJsonBooleanJsonNull,实现 accept(visitor: JsonVisitor)toString()
  • 撰写一份团队密封类使用规范:何时用密封类 vs 枚举、何时用密封接口、嵌套层级限制、when 表达式必须穷举(禁用 else)等。
  • 设计一个跨平台 KMP 的”网络层”:用密封接口定义 ApiResult,共享模块返回 Flow<ApiResult<T>>,各平台 UI 层用 when 处理。
  • 实现一个”领域事件溯源”框架:用密封类定义所有领域事件,提供 apply(event) 方法用 when 穷举更新聚合根状态。

2. 历史动机与发展脉络

2.1 问题背景:开放继承与类型安全的矛盾

面向对象语言的传统继承模型是”开放的”(open inheritance):任何类都可以被任意子类继承(除非显式标记 finalsealed)。这种开放性带来了严重的类型安全问题,尤其在模式匹配场景下:

// 开放继承下的隐患
open class Shape
class Circle(val radius: Double) : Shape()
class Square(val side: Double) : Shape()

fun area(shape: Shape): Double = when (shape) {
    is Circle -> Math.PI * shape.radius * shape.radius
    is Square -> shape.side * shape.side
    // 问题:新增 Triangle 时,编译器不会提示这里需要更新
    else -> 0.0  // 兜底,掩盖了遗漏
}

开放继承的核心问题:

  1. 编译器无法穷举:开放继承下,子类集合是开放的,编译器无法知道所有可能类型,无法强制 when 覆盖完整。
  2. 遗忘性扩展:新增子类时,所有 when 处理代码都需要手动检查,遗漏即产生 bug。
  3. else 分支的陷阱:为应对开放继承,开发者被迫添加 else 分支,但这会掩盖未来的遗漏。
  4. 重构困难:删除一个子类时,所有相关 when 中的分支变为死代码,难以发现。

2.2 学术背景:代数数据类型与穷举检查

密封类的思想根植于函数式编程语言中的代数数据类型(ADT)理论:

  • ML 语言(1973):引入 datatype,如 datatype expr = Const of real | Sum of expr * expr | Mul of expr * exprmatch 表达式强制穷举。
  • Haskell(1990)data Expr = Const Double | Sum Expr Expr | Mul Expr Expr | Neg Exprcase ... of ... 强制穷举,编译器在新增构造器时警告所有未更新的 case
  • OCaml(1996)type expr = Const of float | Sum of expr * expr,模式匹配强制穷举。
  • Scala(2004)sealed trait Expr; case class Const(value: Double) extends Exprsealed 关键字限制同文件继承,match 强制穷举。
  • Rust(2010)enum Expr { Const(f64), Sum(Box<Expr>, Box<Expr>), Mul(Box<Expr>, Box<Expr>), Neg(Box<Expr>) }match 强制穷举,支持绑定模式。
  • Swift(2014)enum Expr { case Const(Double), Sum(Expr, Expr), Mul(Expr, Expr), Neg(Expr) }switch 强制穷举。
  • Java 17(2021):引入 sealed classpermits 子句,switch 模式匹配支持穷举检查。

Kotlin 的设计选择:

  • sealed 关键字:借鉴 Scala,简洁明确。
  • when 表达式穷举:借鉴 Scala match、Haskell case
  • 不引入绑定模式:与 Scala/Rust 不同,Kotlin 用 is + 智能转换替代绑定模式,简洁性有余但表达力稍弱。
  • data class 作为积类型:借鉴 Scala case class,自动生成 equalshashCodecopy

2.3 Kotlin 1.0(2016):密封类初版

Kotlin 1.0 引入密封类,但限制严格:

// Kotlin 1.0
sealed class Expr {
    data class Const(val value: Double) : Expr()
    data class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Double = when (e) {
    is Expr.Const -> e.value
    is Expr.Sum -> eval(e.left) + eval(e.right)
    // 穷举检查:编译器知道只有 Const 与 Sum 两类
}

1.0 的限制:

  1. 子类必须在同一文件中声明。
  2. 子类必须直接嵌套在密封类内部(不能跨文件、不能跨包)。
  3. 不支持密封接口(sealed interface)。
  4. 穷举检查仅在 when 作为表达式使用时生效。

2.4 Kotlin 1.1(2017):子类位置灵活化

Kotlin 1.1 放宽了子类位置限制:

// 同文件内,子类可在密封类外部声明
sealed class Shape

class Circle(val radius: Double) : Shape()
class Square(val side: Double) : Shape()

但仍要求:

  1. 子类与密封类在同一文件。
  2. 子类必须直接继承密封类(不能跨中间抽象层)。

2.5 Kotlin 1.5(2021):跨文件与密封接口

Kotlin 1.5 是密封类型演进的里程碑:

  1. 跨文件子类:允许密封类的子类在同一包内任意文件中声明,支持大型项目模块化拆分。
// 文件:shapes/Circle.kt
package com.example.shapes
class Circle(val radius: Double) : Shape()

// 文件:shapes/Square.kt
package com.example.shapes
class Square(val side: Double) : Shape()

// 文件:shapes/Shape.kt
package com.example.shapes
sealed class Shape
  1. 密封接口(sealed interface:引入密封接口,支持多继承与枚举实现。
sealed interface Drawable {
    fun draw()
}

sealed interface Clickable {
    fun onClick()
}

// 一个类可同时实现多个密封接口
data class Button(val label: String) : Drawable, Clickable {
    override fun draw() { /* ... */ }
    override fun onClick() { /* ... */ }
}

// 枚举也可实现密封接口
enum class Color : Drawable {
    RED, GREEN, BLUE;
    override fun draw() { /* ... */ }
}
  1. 穷举检查改进when 表达式穷举检查覆盖密封接口的所有实现类。

2.6 Kotlin 1.6-1.7(2021-2022):细节优化

1.6-1.7 对密封类型进行了细节优化:

  1. 密封接口嵌套:允许密封接口嵌套在密封类或密封接口内。
  2. @SerialName 与密封类kotlinx.serialization 改进对密封类的多态序列化支持。
  3. 智能转换精度:K2 预览编译器改进了 is 检查后的智能转换。

2.7 Kotlin 1.9(2023):K2 兼容

Kotlin 1.9 的 K2 编译器 Beta 版本对密封类型进行了兼容性测试:

  1. 穷举检查精度:K2 能识别 when 嵌套、is + !is 组合等边界情况。
  2. 错误信息改进:K2 提供更精确的穷举失败提示,列出缺失的子类。
  3. 跨模块检查:K2 在跨模块使用密封类时也能正确检查穷举。

2.8 Kotlin 2.0(2024):K2 全面成熟

Kotlin 2.0 的 K2 编译器对密封类型进行了全面优化:

  1. 编译速度:K2 通过 FIR(Frontend Intermediate Representation)一次分析子类列表,避免 K1 的重复扫描。
  2. 穷举检查智能化:K2 能识别 when 表达式中 null 分支、is 与对象单例的组合、嵌套密封类的传播。
  3. 跨平台一致性:KMP 项目中 JVM、JS、Native 平台的穷举检查行为完全一致。
  4. 更好的错误信息:K2 能精确指出缺失的子类名称,并支持 Quick Fix 自动生成 when 分支骨架。

2.9 与 Java 17 sealed 的对比

Java 17(2021 年 9 月)引入了 sealed 关键字与 permits 子句:

// Java 17
public sealed abstract class Shape permits Circle, Square, Triangle {
}

final class Circle extends Shape { /* ... */ }
final class Square extends Shape { /* ... */ }
final class Triangle extends Shape { /* ... */ }

Java 17 sealed 与 Kotlin sealed 的对比:

特性Java 17 sealedKotlin sealed
子类声明位置permits 子句显式列出同包自动收集
子类可见性要求必须与父类同模块或同包必须与父类同包
sealed interface支持支持(1.5+)
穷举检查switch 模式匹配(Java 21+)when 表达式
子类修饰符限制finalsealednon-sealed任意(classdata classobject
Kotlin 互操作可作普通继承使用,但 Kotlin 不识别完全支持

2.10 时间线总览

1973  ML — 引入 datatype,函数式 ADT 开端
1990  Haskell — data 声明,case 穷举
1996  OCaml — type 声明,match 穷举
2004  Scala — sealed trait,case class,match 穷举
2010  Rust — enum + match,支持绑定模式
2014  Swift — enum + switch 穷举
2016  Kotlin 1.0 — sealed class,when 穷举(同文件)
2017  Kotlin 1.1 — 子类可在同文件任意位置
2021  Kotlin 1.5 — 跨文件子类 + sealed interface
2021  Java 17 — sealed class + permits
2022  Kotlin 1.7 — 密封接口嵌套
2023  Kotlin 1.9 — K2 Beta,穷举检查精度提升
2024  Kotlin 2.0 — K2 GA,穷举检查智能化

3. 形式化定义

3.1 密封类的形式化定义

T\mathcal{T} 为 Kotlin 类型集合,ST\mathcal{S} \subseteq \mathcal{T} 为密封类集合。密封类 SSS \in \mathcal{S} 的形式化定义为一个三元组:

S:=(N,Σ,C)S := (N, \Sigma, \mathcal{C})

其中:

  • NN 是密封类的名字(如 Result)。
  • Σ\Sigma 是类型参数集合(如 {T}\{T\},可能为空)。
  • C={C1,C2,,Cn}\mathcal{C} = \{C_1, C_2, \ldots, C_n\} 是密封类的所有直接子类(children)的有限集合,且 C1|\mathcal{C}| \geq 1

关键约束:

  1. 封闭性C\mathcal{C} 是有限的且在编译期完全已知。
  2. 不可实例化SS 本身是抽象的,不能直接 S() 实例化。
  3. 不可扩展:除 C\mathcal{C} 中的子类外,任何其他类不能继承 SS
  4. 位置约束:所有 CiCC_i \in \mathcal{C} 必须与 SS 在同一包(Kotlin 1.5+)或同一文件(Kotlin 1.0-1.4)。

3.2 代数数据类型(ADT)视角

从代数数据类型视角,密封类对应”和类型”(Sum Type),data class 对应”积类型”(Product Type)。

和类型(Sum Type)

S=C1  +  C2  +    +  CnS = C_1 \;+\; C_2 \;+\; \cdots \;+\; C_n

表示 SS 的值”是 C1C_1C2C_2 或 … 或 CnC_n 中的一种”。

积类型(Product Type)

Ci=Ti,1×Ti,2××Ti,kiC_i = T_{i,1} \times T_{i,2} \times \cdots \times T_{i,k_i}

表示 CiC_i 的值”是 Ti,1T_{i,1}Ti,2T_{i,2} 与 … 与 Ti,kiT_{i,k_i} 的组合”。

示例

sealed class Expr  // 和类型
data class Const(val value: Double) : Expr()  // 积类型:Double
data class Sum(val left: Expr, val right: Expr) : Expr()  // 积类型:Expr × Expr
data class Mul(val left: Expr, val right: Expr) : Expr()  // 积类型:Expr × Expr
object NegZero : Expr()  // 单位类型(Unit Type)

形式化:

Expr=Const(Double)+Sum(Expr,Expr)+Mul(Expr,Expr)+NegZero\text{Expr} = \text{Const}(\text{Double}) + \text{Sum}(\text{Expr}, \text{Expr}) + \text{Mul}(\text{Expr}, \text{Expr}) + \text{NegZero}

这是递归定义的代数数据类型。

3.3 穷举检查的形式化

S=(N,Σ,C)S = (N, \Sigma, \mathcal{C}) 是密封类,WWwhen 表达式,B\mathcal{B}WW 中的分支集合。每个分支 bBb \in \mathcal{B} 通过 is 检查匹配若干子类。

穷举检查规则

Exhaustive(W,S)    bBMatches(b)=CbB:b contains else\text{Exhaustive}(W, S) \iff \bigcup_{b \in \mathcal{B}} \text{Matches}(b) = \mathcal{C} \lor \exists b \in \mathcal{B}: b \text{ contains } \texttt{else}

其中 Matches(b)\text{Matches}(b) 是分支 bb 匹配的子类集合。

编译期错误:若 when 作为表达式(有返回值)且无 else 分支,而 bBMatches(b)C\bigcup_{b \in \mathcal{B}} \text{Matches}(b) \neq \mathcal{C},编译器报错:

error: when expression must be exhaustive. Add the missing branch or else branch.

嵌套密封类的穷举

CiCC_i \in \mathcal{C} 本身是密封类 Ci=(Ni,Σi,Ci)C_i = (N_i, \Sigma_i, \mathcal{C}_i),则穷举检查要求:

Matches(bi)=Cicontains else\text{Matches}(b_i) = \mathcal{C}_i \lor \text{contains else}

即必须继续穷举 CiC_i 的子类,或用 else 兜底。

3.4 子类型关系

密封类的子类型关系 \sqsubseteq

CiC,CiS\forall C_i \in \mathcal{C}, \quad C_i \sqsubseteq S

且(若 SS 是泛型,带型变):

CiC,Ci<Nothing>S<T>if S is covariant in T\forall C_i \in \mathcal{C}, \quad C_i<\text{Nothing}> \sqsubseteq S<T> \quad \text{if } S \text{ is covariant in } T

示例Result<Nothing>Result<T> 的子类型(协变):

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>()
}

// Result<Nothing> 是 Result<Int> 的子类型
val r: Result<Int> = Result.Loading  // OK

3.5 when 表达式的形式化语义

when 表达式的形式化语义:

when(x){b1,b2,,bn}={eval(bi)if matches(bi,x) and j<i:¬matches(bj,x)undefinedif no branch matches and no else\text{when}(x) \{ b_1, b_2, \ldots, b_n \} = \begin{cases} \text{eval}(b_i) & \text{if } \text{matches}(b_i, x) \text{ and } \forall j < i: \neg \text{matches}(b_j, x) \\ \text{undefined} & \text{if no branch matches and no else} \end{cases}

其中 matches(b,x)\text{matches}(b, x) 的定义取决于分支类型:

  • is T ->matches(b,x):=x is T\text{matches}(b, x) := x \text{ is } T
  • value ->matches(b,x):=x=value\text{matches}(b, x) := x = \text{value}
  • else ->matches(b,x):=true\text{matches}(b, x) := \text{true}(总是匹配)

分支顺序when 表达式按声明顺序匹配,第一个匹配的分支被执行。这意味着更具体的分支应放在更一般的分支之前。

3.6 密封接口的形式化定义

密封接口 II 的形式化定义:

I:=(N,Σ,M,R)I := (N, \Sigma, \mathcal{M}, \mathcal{R})

其中:

  • NN 是接口名字。
  • Σ\Sigma 是类型参数集合。
  • M\mathcal{M} 是接口声明的抽象方法集合。
  • R\mathcal{R} 是所有直接实现类(realizations)的有限集合,且 R1|\mathcal{R}| \geq 1

关键约束:

  1. R\mathcal{R} 中的元素可以是 classdata classobjectenum class 或其他密封接口。
  2. 一个类可同时属于多个密封接口的 R\mathcal{R} 集合(多继承)。
  3. 所有 rRr \in \mathcal{R} 必须实现 M\mathcal{M} 中的所有抽象方法(除非 rr 本身是抽象的)。

3.7 智能转换的形式化

when 分支 is T -> 内,编译器对变量 xx 进行类型细化(type narrowing):

Γ,x:Sx is T    Γ,x:T\Gamma, x : S \vdash x \text{ is } T \implies \Gamma, x : T

即在 is T 成立的分支内,xx 的类型从 SS 细化为 TT

多重细化:在嵌套 whenis + 条件组合时,编译器维护累加的类型细化信息:

val result: Result<Int> = ...
when (result) {
    is Result.Success -> {
        // result: Result.Success<Int>
        if (result.value > 0) {
            // result.value: Int 且 > 0
        }
    }
}

3.8 JVM 字节码层面的表示

在 JVM 字节码层面,密封类是普通的抽象类,但携带 @Metadata 注解:

@Metadata(
    kotlin.KotlinMetadata(...),
    // d2 数组包含子类列表
    d2 = {"Lcom/example/Result;", "Success", "Error", "Loading", ...}
)
public abstract class Result {
    // 抽象类,无特殊字节码
}

@Metadata 注解的 d2 数组以字符串形式存储子类的 JVM 内部类名,供 Kotlin 编译器跨模块检查穷举。

Java 调用者视角:Java 代码将密封类视为普通抽象类,可继承(但不会触发穷举检查):

// Java 代码可以继承 Kotlin 密封类(不推荐)
public class JavaResult extends Result { /* ... */ }

Kotlin 编译器会发出警告:This class inherits from a sealed class, but no corresponding @Metadata sub-class declaration is found.

3.9 密封类的型变

密封类支持泛型与型变(variance):

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>()
}

形式化:

Result<+T>:=Success<T>+Error+Loading\text{Result}<+T> := \text{Success}<T> + \text{Error} + \text{Loading}

协变规则保证:

Result<Nothing>Result<Int>\text{Result}<\text{Nothing}> \sqsubseteq \text{Result}<\text{Int}>

这使得 LoadingError(无值分支)可统一赋给任意 Result<T>

3.10 递归密封类的形式化

递归密封类(如表达式树)的形式化:

E=C(Double)+S(E,E)+M(E,E)+N(E)E = C(\text{Double}) + S(E, E) + M(E, E) + N(E)

其中 EE 是密封类 ExprC,S,M,NC, S, M, N 是其子类。这是递归方程,定义了无限深度的树结构。

Catamorphism(折叠):递归 when 求值是 catamorphism 的实例:

eval:EDouble\text{eval} : E \to \text{Double} eval(C(v))=v\text{eval}(C(v)) = v eval(S(l,r))=eval(l)+eval(r)\text{eval}(S(l, r)) = \text{eval}(l) + \text{eval}(r) eval(M(l,r))=eval(l)×eval(r)\text{eval}(M(l, r)) = \text{eval}(l) \times \text{eval}(r) eval(N(e))=eval(e)\text{eval}(N(e)) = -\text{eval}(e)

4. 理论推导与原理解析

4.1 密封类如何解决开放继承问题

问题:开放继承下,when 无法穷举,新增子类不报错。

解决:密封类在编译期固定子类集合 C\mathcal{C},编译器比对 C\mathcal{C}when 分支:

sealed class Shape
class Circle(val radius: Double) : Shape()
class Square(val side: Double) : Shape()

fun area(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.radius * s.radius
    is Square -> s.side * s.side
    // 编译器知道 C = {Circle, Square},分支已穷举,无需 else
}

新增 Triangle 时:

class Triangle(val base: Double, val height: Double) : Shape()
// 此时 C = {Circle, Square, Triangle}

fun area(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.radius * s.radius
    is Square -> s.side * s.side
    // 编译错误:when 表达式必须穷举,缺少 Triangle 分支
}

编译器强制开发者更新 area,避免遗漏。

4.2 穷举检查的实现机制

Kotlin 1.x(K1 编译器)

  1. 子类收集:编译器扫描密封类所在包的所有 .kt 文件,收集所有直接继承密封类的类。
  2. 元数据生成:将子类列表序列化到 @Metadata 注解的 d2 数组。
  3. when 检查:编译器解析 when 分支的 is 检查,与子类列表对比,缺失即报错。

Kotlin 2.0(K2 编译器)

  1. FIR 一次分析:K2 通过 FIR(Frontend Intermediate Representation)一次分析收集子类,避免 K1 的重复扫描。
  2. 跨模块检查:K2 在跨模块使用密封类时,从依赖的 @Metadata 注解读取子类列表,正确检查穷举。
  3. 智能分支合并:K2 能识别 is A -> ...; is B -> ...is A, is B -> ... 的等价性,减少误报。

4.3 嵌套密封类的穷举传播

嵌套密封类的穷举检查规则:

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 -> when (state) {  // 嵌套 when 继续穷举 Content
        is UiState.Content.UserList -> showUserList(state.users)
        is UiState.Content.UserDetail -> showUserDetail(state.user)
    }
    is UiState.Error -> when (state) {  // 嵌套 when 继续穷举 Error
        is UiState.Error.Network -> showNetworkError(state.code)
        is UiState.Error.Auth -> showAuthError(state.reason)
    }
}

编译器要求:

  1. 外层 when 必须覆盖 LoadingContentError
  2. 内层 when 必须覆盖 Content 的子类与 Error 的子类(若内层 when 是表达式)。

替代写法(扁平化):

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)
}

这种写法更简洁,编译器同样能识别穷举。

4.4 密封接口的多继承价值

密封接口支持多继承,这是密封类无法实现的:

sealed interface Drawable { fun draw() }
sealed interface Clickable { fun onClick() }
sealed interface Focusable { fun onFocus() }

data class Button(val label: String) : Drawable, Clickable, Focusable {
    override fun draw() { /* ... */ }
    override fun onClick() { /* ... */ }
    override fun onFocus() { /* ... */ }
}

data class Image(val url: String) : Drawable {
    override fun draw() { /* ... */ }
}

// 在不同的 when 中分别处理
fun handleDraw(d: Drawable) = when (d) {
    is Button -> drawButton(d)
    is Image -> drawImage(d)
}

fun handleClick(c: Clickable) = when (c) {
    is Button -> handleButtonClick(c)
}

每个密封接口的 when 独立检查穷举,新增 Image 不会影响 Clickablewhen

4.5 when 表达式 vs when 语句

when 作为表达式(有返回值)时强制穷举:

// 表达式:强制穷举
val msg: String = when (state) {
    is Loading -> "Loading"
    is Success -> "Success"
    is Error -> "Error"
}

when 作为语句(无返回值)时不强制穷举:

// 语句:不强制穷举
when (state) {
    is Loading -> showLoading()
    // 不报错,但可能遗漏 Success 与 Error
}

最佳实践:始终将 when 作为表达式使用,借助 val result = when (state) { ... } 强制穷举,即使不需要返回值。

4.6 object 子类与 data class 子类的差异

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>()
}

fun handle(r: Result<Int>) = when (r) {
    is Result.Success -> println(r.value)  // r: Result.Success<Int>
    is Result.Error -> println(r.message)  // r: Result.Error
    Result.Loading -> println("Loading")    // r: Result.Loading(object 单例)
    // 注意:object 子类用 Result.Loading(不带 is),因为单例可直接比较
}

is vs 直接引用

  • is 用于类型检查,适用于 data classclass 子类。
  • 直接引用(如 Result.Loading)用于 object 单例比较,等价于 ===
  • 两者均可触发穷举检查。

4.7 智能转换的边界

智能转换在 when 分支内有效,但有以下限制:

  1. var 变量:智能转换可能失效(多线程下 var 可变)。
  2. 跨函数:智能转换不跨函数边界。
  3. 复杂表达式is 后跟 &&|| 时,智能转换可能不传播。
var state: Result<Int> = ...

when (state) {
    is Result.Success -> {
        // state: Result.Success<Int>?不一定!
        // 因为 state 是 var,可能在 is 检查后被其他线程修改
        // 但 Kotlin 在单线程上下文(如局部 var)中仍允许智能转换
        println(state.value)
    }
}

安全实践:对于可变属性,先赋给局部 val

val currentState = state  // 快照
when (currentState) {
    is Result.Success -> println(currentState.value)  // 安全
}

4.8 密封类与 when 的编译器优化

Kotlin 编译器对 when 进行优化:

  1. tableswitch 优化:若子类数量较多且分支条件是整数比较,编译器生成 tableswitch 字节码,O(1)O(1) 跳转。
  2. lookupswitch 优化:若分支条件是字符串或稀疏整数,编译器生成 lookupswitch
  3. if-else 链回退:若无法优化为 switch,编译器生成 if-else 链,O(n)O(n) 比较。

示例

sealed class Color { object RED; object GREEN; object BLUE }
fun name(c: Color) = when (c) {
    Color.RED -> "Red"
    Color.GREEN -> "Green"
    Color.BLUE -> "Blue"
}

字节码(简化):

public static String name(Color c) {
    int id = c instanceof Color.RED ? 0 : (c instanceof Color.GREEN ? 1 : 2);
    switch (id) {
        case 0: return "Red";
        case 1: return "Green";
        case 2: return "Blue";
    }
}

4.9 密封类与递归数据结构

递归密封类(如表达式树、JSON 树)的形式化:

sealed class Json {
    data class Obj(val entries: Map<String, Json>) : Json()
    data class Arr(val elements: List<Json>) : Json()
    data class Str(val value: String) : Json()
    data class Num(val value: Double) : Json()
    data class Bool(val value: Boolean) : Json()
    object Null : Json()
}

fun stringify(json: Json): String = when (json) {
    is Json.Obj -> json.entries.entries.joinToString(",", "{", "}") { "\"${it.key}\":${stringify(it.value)}" }
    is Json.Arr -> json.elements.joinToString(",", "[", "]") { stringify(it) }
    is Json.Str -> "\"${json.value}\""
    is Json.Num -> json.value.toString()
    is Json.Bool -> json.value.toString()
    Json.Null -> "null"
}

递归 when 是 catamorphism(折叠)的实例,对树结构进行递归遍历。

4.10 Kotlin 2.0 K2 编译器的穷举检查改进

K2 编译器对穷举检查进行了多项改进:

  1. null 分支识别:对可空密封类,K2 能识别 null 分支与 is 分支的组合:
sealed class Result
object Loading : Result()
data class Success(val value: Int) : Result()

fun handle(r: Result?) = when (r) {
    null -> println("Null")
    Result.Loading -> println("Loading")
    is Result.Success -> println(r.value)
}
// K2 识别 null 分支,穷举检查通过
  1. is + !is 组合:K2 能识别 is A!is A 的互补关系。

  2. when 嵌套穷举传播:K2 能识别嵌套 when 的穷举传播,减少误报。

  3. 跨模块一致性:K2 在 KMP 项目中保证 JVM、JS、Native 平台的穷举检查行为一致。


5. 代码示例

5.1 网络请求结果建模

/**
 * 网络请求结果的三态建模。
 * - Loading:请求进行中
 * - Success:请求成功,携带数据
 * - Error:请求失败,携带错误信息
 */
sealed class NetworkResult<out T> {
    object Loading : NetworkResult<Nothing>()
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String, val cause: Throwable? = null) : NetworkResult<Nothing>()
}

/**
 * 处理网络请求结果,穷举所有分支。
 */
fun <T> handleResult(result: NetworkResult<T>) {
    when (result) {
        NetworkResult.Loading -> println("Loading...")
        is NetworkResult.Success -> println("Success: ${result.data}")
        is NetworkResult.Error -> {
            println("Error: ${result.message}")
            result.cause?.printStackTrace()
        }
    }
}

fun main() {
    handleResult(NetworkResult.Loading)
    handleResult(NetworkResult.Success(42))
    handleResult(NetworkResult.Error("Network error"))
}

5.2 UI 状态建模(MVI 架构)

/**
 * 屏幕状态建模,覆盖空闲、加载、内容、错误四态。
 */
sealed class UiState<out T> {
    object Idle : UiState<Nothing>()
    object Loading : UiState<Nothing>()
    data class Content<T>(val data: T) : UiState<T>()
    data class Error(val message: String, val retry: () -> Unit) : UiState<Nothing>()
}

/**
 * 用户意图(Wish / Intent),MVI 架构的核心。
 */
sealed class UserWish {
    data class LoadUser(val id: String) : UserWish()
    data class UpdateUser(val name: String) : UserWish()
    object Refresh : UserWish()
    object Logout : UserWish()
}

/**
 * 副作用(Effect),如导航、显示 Toast。
 */
sealed class UserEffect {
    data class ShowToast(val message: String) : UserEffect()
    data class Navigate(val route: String) : UserEffect()
}

class UserViewModel : ViewModel() {
    private val _state = MutableStateFlow<UiState<User>>(UiState.Idle)
    val state: StateFlow<UiState<User>> = _state.asStateFlow()

    private val _effects = Channel<UserEffect>(Channel.BUFFERED)
    val effects = _effects.receiveAsFlow()

    fun accept(wish: UserWish) {
        when (wish) {
            is UserWish.LoadUser -> loadUser(wish.id)
            is UserWish.UpdateUser -> updateUser(wish.name)
            UserWish.Refresh -> refresh()
            UserWish.Logout -> {
                _state.value = UiState.Idle
                _effects.trySend(UserEffect.Navigate("/login"))
            }
        }
    }

    private fun loadUser(id: String) {
        viewModelScope.launch {
            _state.value = UiState.Loading
            _state.value = try {
                UiState.Content(repository.fetchUser(id))
            } catch (e: Exception) {
                UiState.Error(e.message ?: "Unknown error") { loadUser(id) }
            }
        }
    }

    private fun updateUser(name: String) { /* ... */ }
    private fun refresh() { /* ... */ }
}

5.3 表达式树求值(递归密封类)

/**
 * 算术表达式的 AST 节点。
 */
sealed class Expr {
    data class Const(val value: Double) : Expr()
    data class Var(val name: String) : 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()
    data class Div(val numerator: Expr, val denominator: Expr) : Expr()
}

/**
 * 求值函数,递归遍历 AST。
 * @param env 变量环境,name -> value
 * @return 表达式的值
 */
fun eval(expr: Expr, env: Map<String, Double> = emptyMap()): Double = when (expr) {
    is Expr.Const -> expr.value
    is Expr.Var -> env[expr.name] ?: error("Undefined variable: ${expr.name}")
    is Expr.Sum -> eval(expr.left, env) + eval(expr.right, env)
    is Expr.Mul -> eval(expr.left, env) * eval(expr.right, env)
    is Expr.Neg -> -eval(expr.expr, env)
    is Expr.Div -> {
        val d = eval(expr.denominator, env)
        if (d == 0.0) error("Division by zero")
        eval(expr.numerator, env) / d
    }
}

/**
 * 表达式简化(代数化简)。
 */
fun simplify(expr: Expr): Expr = when (expr) {
    is Expr.Sum -> {
        val l = simplify(expr.left)
        val r = simplify(expr.right)
        when {
            l is Expr.Const && l.value == 0.0 -> r
            r is Expr.Const && r.value == 0.0 -> l
            l is Expr.Const && r is Expr.Const -> Expr.Const(l.value + r.value)
            else -> Expr.Sum(l, r)
        }
    }
    is Expr.Mul -> {
        val l = simplify(expr.left)
        val r = simplify(expr.right)
        when {
            l is Expr.Const && l.value == 0.0 -> Expr.Const(0.0)
            r is Expr.Const && r.value == 0.0 -> Expr.Const(0.0)
            l is Expr.Const && l.value == 1.0 -> r
            r is Expr.Const && r.value == 1.0 -> l
            l is Expr.Const && r is Expr.Const -> Expr.Const(l.value * r.value)
            else -> Expr.Mul(l, r)
        }
    }
    is Expr.Neg -> {
        val e = simplify(expr.expr)
        when (e) {
            is Expr.Const -> Expr.Const(-e.value)
            is Expr.Neg -> e.expr  // 双重否定消除
            else -> Expr.Neg(e)
        }
    }
    else -> expr
}

fun main() {
    // (1 + 2) * (3 + 4) = 21
    val expr = Expr.Mul(
        Expr.Sum(Expr.Const(1.0), Expr.Const(2.0)),
        Expr.Sum(Expr.Const(3.0), Expr.Const(4.0))
    )
    println(eval(expr))  // 21.0

    // 简化:0 + x = x
    val simplified = simplify(Expr.Sum(Expr.Const(0.0), Expr.Var("x")))
    println(simplified)  // Var(name=x)
}

5.4 JSON 树建模与遍历

/**
 * JSON 数据结构的 AST。
 */
sealed class Json {
    data class Obj(val entries: List<Pair<String, Json>>) : Json() {
        operator fun get(key: String): Json? = entries.firstOrNull { it.first == key }?.second
    }
    data class Arr(val elements: List<Json>) : Json()
    data class Str(val value: String) : Json()
    data class Num(val value: Double) : Json()
    data class Bool(val value: Boolean) : Json()
    object Null : Json()
}

/**
 * JSON 序列化为字符串。
 */
fun Json.stringify(indent: Int = 0): String {
    val pad = "  ".repeat(indent)
    return when (this) {
        is Json.Obj -> "{\n" + entries.joinToString(",\n") { "$pad  \"${it.first}\": ${it.second.stringify(indent + 1)}" } + "\n$pad}"
        is Json.Arr -> "[\n" + elements.joinToString(",\n") { "$pad  ${it.stringify(indent + 1)}" } + "\n$pad]"
        is Json.Str -> "\"$value\""
        is Json.Num -> value.toString()
        is Json.Bool -> value.toString()
        Json.Null -> "null"
    }
}

/**
 * 访问者模式遍历 JSON。
 */
fun Json.accept(visitor: JsonVisitor) {
    when (this) {
        is Json.Obj -> {
            visitor.visitObject(this)
            entries.forEach { (_, v) -> v.accept(visitor) }
        }
        is Json.Arr -> {
            visitor.visitArray(this)
            elements.forEach { it.accept(visitor) }
        }
        is Json.Str -> visitor.visitString(this)
        is Json.Num -> visitor.visitNumber(this)
        is Json.Bool -> visitor.visitBoolean(this)
        Json.Null -> visitor.visitNull()
    }
}

interface JsonVisitor {
    fun visitObject(obj: Json.Obj) {}
    fun visitArray(arr: Json.Arr) {}
    fun visitString(str: Json.Str) {}
    fun visitNumber(num: Json.Num) {}
    fun visitBoolean(bool: Json.Bool) {}
    fun visitNull() {}
}

fun main() {
    val json = Json.Obj(
        listOf(
            "name" to Json.Str("Alice"),
            "age" to Json.Num(30.0),
            "hobbies" to Json.Arr(listOf(Json.Str("Reading"), Json.Str("Hiking"))),
            "active" to Json.Bool(true)
        )
    )
    println(json.stringify())
}

5.5 密封接口多继承

/**
 * 多继承密封接口示例。
 */
sealed interface Drawable { fun draw(): String }
sealed interface Clickable { fun onClick(): String }
sealed interface Focusable { fun onFocus(): String }

data class Button(val label: String) : Drawable, Clickable, Focusable {
    override fun draw() = "Button($label) drawn"
    override fun onClick() = "Button($label) clicked"
    override fun onFocus() = "Button($label) focused"
}

data class Image(val url: String) : Drawable {
    override fun draw() = "Image($url) drawn"
}

data class TextField(val text: String) : Drawable, Clickable, Focusable {
    override fun draw() = "TextField drawn"
    override fun onClick() = "TextField clicked"
    override fun onFocus() = "TextField focused"
}

object Divider : Drawable {
    override fun draw() = "Divider drawn"
}

/**
 * 处理可绘制对象,穷举所有 Drawable 子类。
 */
fun render(d: Drawable): String = when (d) {
    is Button -> "Rendering: ${d.draw()}"
    is Image -> "Rendering: ${d.draw()}"
    is TextField -> "Rendering: ${d.draw()}"
    Divider -> "Rendering: ${d.draw()}"
}

/**
 * 处理可点击对象,穷举所有 Clickable 子类。
 * 注意:Image 与 Divider 不实现 Clickable,不在分支中。
 */
fun handleClick(c: Clickable): String = when (c) {
    is Button -> c.onClick()
    is TextField -> c.onClick()
}

fun main() {
    println(render(Button("Submit")))    // Rendering: Button(Submit) drawn
    println(render(Image("logo.png")))   // Rendering: Image(logo.png) drawn
    println(handleClick(Button("OK")))   // Button(OK) clicked
}

5.6 状态机建模

/**
 * 订单状态机。
 */
sealed class OrderState {
    object Created : OrderState()
    data class Paid(val amount: Double, val paidAt: Long) : OrderState()
    data class Shipped(val trackingNo: String, val shippedAt: Long) : OrderState()
    data class Delivered(val deliveredAt: Long) : OrderState()
    data class Cancelled(val reason: String, val cancelledAt: Long) : OrderState()
}

/**
 * 状态转换事件。
 */
sealed class OrderEvent {
    data class Pay(val amount: Double) : OrderEvent()
    data class Ship(val trackingNo: String) : OrderEvent()
    object Deliver : OrderEvent()
    data class Cancel(val reason: String) : OrderEvent()
}

/**
 * 状态转换函数,穷举所有 state × event 组合。
 * 不可能的转换抛出 IllegalStateException。
 */
fun transition(state: OrderState, event: OrderEvent): OrderState = when (state) {
    OrderState.Created -> when (event) {
        is OrderEvent.Pay -> OrderState.Paid(event.amount, System.currentTimeMillis())
        is OrderEvent.Cancel -> OrderState.Cancelled(event.reason, System.currentTimeMillis())
        is OrderEvent.Ship -> error("Cannot ship unpaid order")
        OrderEvent.Deliver -> error("Cannot deliver unpaid order")
    }
    is OrderState.Paid -> when (event) {
        is OrderEvent.Ship -> OrderState.Shipped(event.trackingNo, System.currentTimeMillis())
        is OrderEvent.Cancel -> OrderState.Cancelled(event.reason, System.currentTimeMillis())
        is OrderEvent.Pay -> error("Order already paid")
        OrderEvent.Deliver -> error("Cannot deliver unshipped order")
    }
    is OrderState.Shipped -> when (event) {
        OrderEvent.Deliver -> OrderState.Delivered(System.currentTimeMillis())
        is OrderEvent.Cancel -> error("Cannot cancel shipped order")
        is OrderEvent.Pay -> error("Order already paid")
        is OrderEvent.Ship -> error("Order already shipped")
    }
    is OrderState.Delivered -> when (event) {
        // 终态,不接受任何事件
        is OrderEvent.Pay, is OrderEvent.Ship, OrderEvent.Deliver, is OrderEvent.Cancel ->
            error("Order already delivered, terminal state")
    }
    is OrderState.Cancelled -> when (event) {
        // 终态,不接受任何事件
        is OrderEvent.Pay, is OrderEvent.Ship, OrderEvent.Deliver, is OrderEvent.Cancel ->
            error("Order already cancelled, terminal state")
    }
}

fun main() {
    var state: OrderState = OrderState.Created
    state = transition(state, OrderEvent.Pay(99.0))
    println(state)  // Paid(amount=99.0, ...)
    state = transition(state, OrderEvent.Ship("SF123456"))
    println(state)  // Shipped(trackingNo=SF123456, ...)
    state = transition(state, OrderEvent.Deliver)
    println(state)  // Delivered(...)
}

5.7 密封类与序列化

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

/**
 * 消息类型的多态序列化。
 */
@Serializable
sealed class Message {
    @Serializable
    @SerialName("text")
    data class Text(val content: String, val author: String) : Message()

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

    @Serializable
    @SerialName("system")
    data class SystemNotice(val action: String, val timestamp: Long) : Message()

    @Serializable
    @SerialName("sticker")
    data class Sticker(val stickerId: String, val author: String) : Message()
}

fun main() {
    val json = Json {
        ignoreUnknownKeys = true
        classDiscriminator = "type"  // 类鉴别器字段名
    }

    // 序列化
    val messages: List<Message> = listOf(
        Message.Text("Hello", "Alice"),
        Message.Image("https://example.com/1.png", 800, 600, "Bob"),
        Message.SystemNotice("user_joined", System.currentTimeMillis()),
        Message.Sticker("sticker_001", "Alice")
    )

    val jsonString = json.encodeToString(messages)
    println(jsonString)

    // 反序列化
    val decoded = json.decodeFromString<List<Message>>(jsonString)
    decoded.forEach { msg ->
        when (msg) {
            is Message.Text -> println("[Text] ${msg.author}: ${msg.content}")
            is Message.Image -> println("[Image] ${msg.author}: ${msg.url}")
            is Message.SystemNotice -> println("[System] ${msg.action} at ${msg.timestamp}")
            is Message.Sticker -> println("[Sticker] ${msg.author}: ${msg.stickerId}")
        }
    }
}

5.8 嵌套密封类分层处理

/**
 * 嵌套密封类:分层建模复杂状态。
 */
sealed class AppState {
    object Splash : AppState()

    sealed class Auth : AppState() {
        object Login : Auth()
        object Register : Auth()
        data class ForgotPassword(val email: String) : Auth()
    }

    sealed class Main : AppState() {
        data class Home(val tabIndex: Int) : Main()
        data class Profile(val userId: String) : Main()
        data class Settings(val section: String) : Main()
    }

    sealed class Error : AppState() {
        data class Network(val code: Int) : Error()
        data class Server(val message: String) : Error()
        object Unknown : Error()
    }
}

/**
 * 分层处理:外层 when 穷举顶层子类,内层 when 穷举嵌套子类。
 */
fun handleState(state: AppState) {
    when (state) {
        AppState.Splash -> showSplash()
        is AppState.Auth -> when (state) {
            is AppState.Auth.Login -> showLogin()
            is AppState.Auth.Register -> showRegister()
            is AppState.Auth.ForgotPassword -> showForgotPassword(state.email)
        }
        is AppState.Main -> when (state) {
            is AppState.Main.Home -> showHome(state.tabIndex)
            is AppState.Main.Profile -> showProfile(state.userId)
            is AppState.Main.Settings -> showSettings(state.section)
        }
        is AppState.Error -> when (state) {
            is AppState.Error.Network -> showNetworkError(state.code)
            is AppState.Error.Server -> showServerError(state.message)
            AppState.Error.Unknown -> showUnknownError()
        }
    }
}

fun showSplash() {}
fun showLogin() {}
fun showRegister() {}
fun showForgotPassword(email: String) {}
fun showHome(tab: Int) {}
fun showProfile(userId: String) {}
fun showSettings(section: String) {}
fun showNetworkError(code: Int) {}
fun showServerError(message: String) {}
fun showUnknownError() {}

5.9 密封类与 @SerialName 多态序列化

/**
 * 跨平台 API 响应的多态序列化。
 */
@Serializable
sealed class ApiResponse<out T> {
    @Serializable
    @SerialName("success")
    data class Success<T>(val data: T, val timestamp: Long) : ApiResponse<T>()

    @Serializable
    @SerialName("error")
    data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()

    @Serializable
    @SerialName("empty")
    object Empty : ApiResponse<Nothing>()
}

@Serializable
data class User(val id: String, val name: String)

fun main() {
    val json = Json { ignoreUnknownKeys = true }

    // 模拟 API 响应
    val successJson = """{"type":"success","data":{"id":"1","name":"Alice"},"timestamp":1700000000}"""
    val errorJson = """{"type":"error","code":404,"message":"Not Found"}"""
    val emptyJson = """{"type":"empty"}"""

    val success: ApiResponse<User> = json.decodeFromString(successJson)
    val error: ApiResponse<User> = json.decodeFromString(errorJson)
    val empty: ApiResponse<User> = json.decodeFromString(emptyJson)

    listOf(success, error, empty).forEach { response ->
        val msg = when (response) {
            is ApiResponse.Success -> "OK: ${response.data}"
            is ApiResponse.Error -> "Error ${response.code}: ${response.message}"
            ApiResponse.Empty -> "Empty response"
        }
        println(msg)
    }
}

5.10 密封接口与枚举组合

/**
 * 密封接口允许枚举作为子类型。
 */
sealed interface Color {
    val rgb: Int
}

enum class BasicColor(override val rgb: Int) : Color {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

data class CustomColor(val name: String, override val rgb: Int) : Color

data class GradientColor(val from: Color, val to: Color) : Color {
    override val rgb: Int = from.rgb  // 简化:使用起始色
}

fun describe(c: Color): String = when (c) {
    BasicColor.RED -> "Red"
    BasicColor.GREEN -> "Green"
    BasicColor.BLUE -> "Blue"
    is CustomColor -> "Custom: ${c.name}"
    is GradientColor -> "Gradient from ${describe(c.from)} to ${describe(c.to)}"
}

fun main() {
    println(describe(BasicColor.RED))  // Red
    println(describe(CustomColor("Magenta", 0xFF00FF)))  // Custom: Magenta
    println(describe(GradientColor(BasicColor.RED, BasicColor.BLUE)))  // Gradient from Red to Blue
}

5.11 密封类与 copy 实现状态更新

/**
 * 表单状态,使用 copy 实现不可变更新。
 */
sealed class FormState {
    data class Empty(val fields: Map<String, String>) : FormState()
    data class PartiallyFilled(val fields: Map<String, String>, val validFields: Int) : FormState()
    data class Complete(val fields: Map<String, String>) : FormState()
    data class Invalid(val fields: Map<String, String>, val errors: List<String>) : FormState()
    object Submitting : FormState()
    data class Submitted(val responseId: String) : FormState()
}

/**
 * 表单事件。
 */
sealed class FormEvent {
    data class UpdateField(val key: String, val value: String) : FormEvent()
    object Validate : FormEvent()
    object Submit : FormEvent()
    data class SubmitSuccess(val responseId: String) : FormEvent()
    data class SubmitError(val error: String) : FormEvent()
}

/**
 * 状态更新函数,穷举所有 state × event 组合。
 */
fun update(state: FormState, event: FormEvent): FormState = when (state) {
    is FormState.Empty -> when (event) {
        is FormEvent.UpdateField -> {
            val newFields = state.fields + (event.key to event.value)
            FormState.PartiallyFilled(newFields, newFields.count { it.value.isNotBlank() })
        }
        FormEvent.Validate -> state  // 无字段可验证
        FormEvent.Submit -> FormState.Invalid(state.fields, listOf("Form is empty"))
        is FormEvent.SubmitSuccess, is FormEvent.SubmitError -> state
    }
    is FormState.PartiallyFilled -> when (event) {
        is FormEvent.UpdateField -> {
            val newFields = state.fields + (event.key to event.value)
            state.copy(fields = newFields, validFields = newFields.count { it.value.isNotBlank() })
        }
        FormEvent.Validate -> {
            val errors = state.fields.filter { it.value.isBlank() }.map { "Field ${it.key} is required" }
            if (errors.isEmpty()) FormState.Complete(state.fields) else FormState.Invalid(state.fields, errors)
        }
        FormEvent.Submit -> FormState.Submitting
        is FormEvent.SubmitSuccess, is FormEvent.SubmitError -> state
    }
    is FormState.Complete -> when (event) {
        is FormEvent.UpdateField -> {
            val newFields = state.fields + (event.key to event.value)
            FormState.PartiallyFilled(newFields, newFields.count { it.value.isNotBlank() })
        }
        FormEvent.Validate -> state  // 已完整
        FormEvent.Submit -> FormState.Submitting
        is FormEvent.SubmitSuccess, is FormEvent.SubmitError -> state
    }
    is FormState.Invalid -> when (event) {
        is FormEvent.UpdateField -> {
            val newFields = state.fields + (event.key to event.value)
            FormState.PartiallyFilled(newFields, newFields.count { it.value.isNotBlank() })
        }
        FormEvent.Validate -> {
            val errors = state.fields.filter { it.value.isBlank() }.map { "Field ${it.key} is required" }
            if (errors.isEmpty()) FormState.Complete(state.fields) else state.copy(errors = errors)
        }
        FormEvent.Submit -> state  // 不允许提交
        is FormEvent.SubmitSuccess, is FormEvent.SubmitError -> state
    }
    FormState.Submitting -> when (event) {
        is FormEvent.SubmitSuccess -> FormState.Submitted(event.responseId)
        is FormEvent.SubmitError -> FormState.Invalid(emptyMap(), listOf(event.error))
        is FormEvent.UpdateField, FormEvent.Validate, FormEvent.Submit -> state  // 提交中忽略其他事件
    }
    is FormState.Submitted -> when (event) {
        // 终态,忽略所有事件
        is FormEvent.UpdateField, FormEvent.Validate, FormEvent.Submit,
        is FormEvent.SubmitSuccess, is FormEvent.SubmitError -> state
    }
}

5.12 密封类实现 Result 类型

/**
 * 类似 Rust Result 的类型,用密封类实现。
 */
sealed class Result<out T, out E> {
    data class Ok<out T>(val value: T) : Result<T, Nothing>()
    data class Err<out E>(val error: E) : Result<Nothing, E>()

    inline fun <R> map(f: (T) -> R): Result<R, E> = when (this) {
        is Ok -> Ok(f(value))
        is Err -> this
    }

    inline fun <R> flatMap(f: (T) -> Result<R, E>): Result<R, E> = when (this) {
        is Ok -> f(value)
        is Err -> this
    }

    inline fun <R> mapError(f: (E) -> R): Result<T, R> = when (this) {
        is Ok -> this
        is Err -> Err(f(error))
    }

    inline fun getOrElse(default: (E) -> T): T = when (this) {
        is Ok -> value
        is Err -> default(error)
    }

    inline fun <R> fold(onOk: (T) -> R, onErr: (E) -> R): R = when (this) {
        is Ok -> onOk(value)
        is Err -> onErr(error)
    }
}

/**
 * 错误类型。
 */
sealed class AppError {
    data class Network(val code: Int, val message: String) : AppError()
    data class Validation(val field: String, val reason: String) : AppError()
    data class Database(val query: String, val cause: Throwable) : AppError()
    object Unauthorized : AppError()
    object NotFound : AppError()
}

/**
 * 模拟用户获取,返回 Result 类型。
 */
fun fetchUser(id: String): Result<User, AppError> {
    if (id.isBlank()) return Result.Err(AppError.Validation("id", "ID cannot be blank"))
    if (id == "404") return Result.Err(AppError.NotFound)
    return Result.Ok(User(id, "User$id"))
}

fun main() {
    val result = fetchUser("1")
        .map { it.copy(name = it.name.uppercase()) }
        .flatMap { user ->
            if (user.name.startsWith("USER")) Result.Ok(user)
            else Result.Err(AppError.Validation("name", "Invalid format"))
        }

    val msg = result.fold(
        onOk = { "Got user: $it" },
        onErr = { err ->
            when (err) {
                is AppError.Network -> "Network error: ${err.code} ${err.message}"
                is AppError.Validation -> "Validation error: ${err.field} - ${err.reason}"
                is AppError.Database -> "Database error: ${err.query}"
                AppError.Unauthorized -> "Unauthorized"
                AppError.NotFound -> "Not Found"
            }
        }
    )
    println(msg)
}

5.13 密封类与 KMP 跨平台

// commonMain/NetworkResult.kt
package com.example.network

/**
 * 跨平台共享的网络结果类型。
 */
sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val code: Int, val message: String) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

// commonMain/ApiService.kt
package com.example.network

interface ApiService {
    suspend fun fetchUser(id: String): NetworkResult<User>
}

// commonMain/ResultProcessor.kt
package com.example.network

/**
 * 跨平台共享的结果处理逻辑。
 */
fun <T> NetworkResult<T>.handle(
    onSuccess: (T) -> Unit,
    onError: (Int, String) -> Unit,
    onLoading: () -> Unit
) {
    when (this) {
        is NetworkResult.Success -> onSuccess(data)
        is NetworkResult.Error -> onError(code, message)
        NetworkResult.Loading -> onLoading()
    }
}

// androidMain/AndroidApiService.kt
package com.example.network

class AndroidApiService(private val retrofit: Retrofit) : ApiService {
    override suspend fun fetchUser(id: String): NetworkResult<User> = try {
        NetworkResult.Success(retrofit.create(UserApi::class.java).getUser(id))
    } catch (e: HttpException) {
        NetworkResult.Error(e.code(), e.message())
    } catch (e: Exception) {
        NetworkResult.Error(-1, e.message ?: "Unknown error")
    }
}

// iosMain/IosApiService.kt
package com.example.network

class IosApiService : ApiService {
    override suspend fun fetchUser(id: String): NetworkResult<User> {
        // iOS 实现,使用 NSURLSession
        TODO("Implement with NSURLSession")
    }
}

5.14 密封类实现命令模式

/**
 * 命令模式:用密封类建模所有用户操作。
 */
sealed class Command {
    data class Create(val key: String, val value: String) : Command()
    data class Update(val key: String, val value: String) : Command()
    data class Delete(val key: String) : Command()
    data class Move(val fromKey: String, val toKey: String) : Command()
    object Undo : Command()
    object Redo : Command()
}

/**
 * 命令执行器,穷举所有命令类型。
 */
class CommandExecutor {
    private val data = mutableMapOf<String, String>()
    private val undoStack = ArrayDeque<() -> Unit>()
    private val redoStack = ArrayDeque<() -> Unit>()

    fun execute(cmd: Command) {
        when (cmd) {
            is Command.Create -> {
                val oldValue = data.put(cmd.key, cmd.value)
                undoStack.addLast { oldValue?.let { data[cmd.key] = it } ?: data.remove(cmd.key) }
            }
            is Command.Update -> {
                val oldValue = data[cmd.key]
                data[cmd.key] = cmd.value
                undoStack.addLast { if (oldValue != null) data[cmd.key] = oldValue }
            }
            is Command.Delete -> {
                val oldValue = data.remove(cmd.key)
                undoStack.addLast { if (oldValue != null) data[cmd.key] = oldValue }
            }
            is Command.Move -> {
                val fromValue = data.remove(cmd.fromKey)
                if (fromValue != null) {
                    val toOldValue = data.put(cmd.toKey, fromValue)
                    undoStack.addLast {
                        if (toOldValue != null) data[cmd.toKey] = toOldValue else data.remove(cmd.toKey)
                        data[cmd.fromKey] = fromValue
                    }
                }
            }
            Command.Undo -> {
                undoStack.removeLastOrNull()?.let { undo ->
                    redoStack.addLast { undo() }  // 简化:实际 undo/redo 更复杂
                    undo()
                }
            }
            Command.Redo -> {
                redoStack.removeLastOrNull()?.invoke()
            }
        }
    }
}

5.15 密封类与递归下降解析器

/**
 * 简单的 Token 类型,用密封类表示。
 */
sealed class Token {
    data class Number(val value: Double) : Token()
    data class Identifier(val name: String) : Token()
    object Plus : Token()
    object Minus : Token()
    object Star : Token()
    object Slash : Token()
    object LParen : Token()
    object RParen : Token()
    object EOF : Token()
}

/**
 * AST 节点。
 */
sealed class Ast {
    data class Number(val value: Double) : Ast()
    data class Var(val name: String) : Ast()
    data class BinOp(val op: String, val left: Ast, val right: Ast) : Ast()
    data class UnaryOp(val op: String, val expr: Ast) : Ast()
}

/**
 * 递归下降解析器。
 */
class Parser(private val tokens: List<Token>) {
    private var pos = 0

    fun parse(): Ast = parseExpr()

    private fun parseExpr(): Ast {
        var left = parseTerm()
        while (peek() is Token.Plus || peek() is Token.Minus) {
            val op = if (next() is Token.Plus) "+" else "-"
            val right = parseTerm()
            left = Ast.BinOp(op, left, right)
        }
        return left
    }

    private fun parseTerm(): Ast {
        var left = parseFactor()
        while (peek() is Token.Star || peek() is Token.Slash) {
            val op = if (next() is Token.Star) "*" else "/"
            val right = parseFactor()
            left = Ast.BinOp(op, left, right)
        }
        return left
    }

    private fun parseFactor(): Ast {
        return when (val t = peek()) {
            is Token.Number -> {
                next()
                Ast.Number(t.value)
            }
            is Token.Identifier -> {
                next()
                Ast.Var(t.name)
            }
            Token.LParen -> {
                next()
                val expr = parseExpr()
                require(next() == Token.RParen) { "Expected )" }
                expr
            }
            Token.Minus -> {
                next()
                Ast.UnaryOp("-", parseFactor())
            }
            Token.Plus, Token.Star, Token.Slash, Token.RParen, Token.EOF ->
                error("Unexpected token: $t")
        }
    }

    private fun peek(): Token = tokens.getOrElse(pos) { Token.EOF }
    private fun next(): Token = tokens[pos++]
}

fun main() {
    val tokens = listOf(
        Token.Number(1.0), Token.Plus, Token.Number(2.0), Token.Star,
        Token.Number(3.0), Token.EOF
    )
    val ast = Parser(tokens).parse()
    println(ast)  // BinOp(op=+, left=Number(1.0), right=BinOp(op=*, ...))
}

6. 对比分析

6.1 密封类 vs 枚举

特性密封类(sealed class)枚举(enum class)
实例数量每个子类可有多个实例每个值只有一个实例(单例)
状态携带子类可携带不同类型与数量数据所有值共享相同属性
继承限制限定在同一包不可继承(默认 final)
类型参数支持泛型(sealed class Result<T>不支持泛型
子类形态data classobjectclassenum
嵌套层级支持任意深度嵌套扁平结构
when 穷举支持支持
适用场景异构状态、ADT、复杂领域模型同构枚举值、状态码、配置选项

选择建议

  • 子类携带不同数据 → 密封类
  • 子类携带相同结构数据 → 枚举
  • 需要泛型 → 密封类
  • 需要单例语义 → 枚举 或 object 子类的密封类
  • 需要嵌套层级 → 密封类

6.2 密封类 vs 密封接口

特性密封类(sealed class)密封接口(sealed interface)
多继承不支持(单继承)支持(一个类可实现多个)
构造器可有(protected/private)不能有
状态字段可有(子类继承)不能有(仅抽象方法)
enum 实现不支持支持(枚举可实现密封接口)
嵌套支持支持
引入版本Kotlin 1.0Kotlin 1.5
适用场景共享状态的类型族跨类型族的协议、能力声明

选择建议

  • 子类需要共享状态或方法实现 → 密封类
  • 子类已继承其他类 → 密封接口
  • 需要枚举作为子类型 → 密封接口
  • 需要多能力组合 → 密封接口
  • 仅为分类,无共享实现 → 密封接口

6.3 密封类 vs 开放类(open class)

特性密封类开放类(open class)
子类范围编译期固定开放,任意继承
when 穷举编译期检查不检查(需 else
扩展性不可扩展(外部模块无法继承)可扩展
类型安全高(编译期穷举)低(运行时多态)
适用场景有限状态、ADT、领域事件框架扩展点、插件体系

6.4 Kotlin 密封类 vs Scala sealed trait

特性KotlinScala
关键字sealed class / sealed interfacesealed trait / sealed abstract class
子类位置同包(1.5+)同文件
穷举检查when 表达式match 表达式
绑定模式不支持(用 is + 智能转换)支持(如 case Some(x) => ...
泛型支持支持
case classdata classcase class

Scala 的优势:绑定模式更强大,可解构嵌套:

sealed trait Option[+T]
case class Some[+T](value: T) extends Option[T]
case object None extends Option[Nothing]

def foo(opt: Option[Option[Int]]) = opt match {
  case Some(Some(x)) => s"Nested: $x"
  case Some(None)    => "Some but None inside"
  case None          => "Nothing"
}

Kotlin 等价写法

sealed class Option<out T>
data class Some<T>(val value: T) : Option<T>()
object None : Option<Nothing>()

fun foo(opt: Option<Option<Int>>): String = when (opt) {
    is Some -> when (opt.value) {
        is Some -> "Nested: ${opt.value.value}"
        None -> "Some but None inside"
    }
    None -> "Nothing"
}

Kotlin 需要嵌套 when,Scala 一行即可,但 Kotlin 写法同样类型安全。

6.5 Kotlin 密封类 vs Rust enum

特性KotlinRust
关键字sealed classenum
子类形态data classobjectclass枚举变体(variants)
绑定模式不支持支持(match 表达式)
泛型支持支持(带类型参数)
递归支持(需 Box 包装避免无限大小)支持(需 Box 包装)
when / matchwhen 表达式match 表达式

Rust 的优势:绑定模式 + 守卫(guard):

enum Expr {
    Const(f64),
    Sum(Box<Expr>, Box<Expr>),
}

fn describe(e: &Expr) -> String {
    match e {
        Expr::Const(v) if *v == 0.0 => "Zero".to_string(),
        Expr::Const(v) => format!("Const({})", v),
        Expr::Sum(l, r) => format!("Sum({}, {})", describe(l), describe(r)),
    }
}

Kotlin 等价(Kotlin 1.7+ 支持 guard):

fun describe(e: Expr): String = when {
    e is Expr.Const && e.value == 0.0 -> "Zero"
    e is Expr.Const -> "Const(${e.value})"
    e is Expr.Sum -> "Sum(${describe(e.left)}, ${describe(e.right)})"
    else -> "Unknown"
}

但 Kotlin 这种写法放弃了穷举检查(用了 when 语句而非 when 表达式)。

6.6 Kotlin 密封类 vs Java 17 sealed

特性KotlinJava 17
关键字sealed classsealed class + permits
子类声明同包自动收集permits 显式列出
子类修饰符任意finalsealednon-sealed
穷举检查when 表达式(Kotlin 1.0+)switch 模式匹配(Java 21+)
密封接口1.5+17+
record 子类data classrecord
Kotlin 互操作原生Kotlin 1.5+ 可识别

6.7 密封类 vs 抽象类 + when

// 方案 A:密封类
sealed class Shape
class Circle(val radius: Double) : Shape()
class Square(val side: Double) : Shape()

fun areaA(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.radius * s.radius
    is Square -> s.side * s.side
    // 编译期穷举检查
}

// 方案 B:抽象类(开放继承)
abstract class ShapeB
class CircleB(val radius: Double) : ShapeB()
class SquareB(val side: Double) : ShapeB()

fun areaB(s: ShapeB): Double = when (s) {
    is CircleB -> Math.PI * s.radius * s.radius
    is SquareB -> s.side * s.side
    else -> 0.0  // 必须有 else,否则编译错误
    // 无穷举检查,新增 TriangleB 时不会报错
}

结论:密封类在编译期保证穷举,开放继承无法保证。优先使用密封类。

6.8 密封类 vs when + else

// 方案 A:密封类 + 无 else(推荐)
fun handleA(state: State): String = when (state) {
    is State.Loading -> "Loading"
    is State.Success -> "Success"
    is State.Error -> "Error"
}

// 方案 B:开放继承 + else(不推荐)
fun handleB(state: Any): String = when (state) {
    is String -> "String"
    is Int -> "Int"
    else -> "Unknown"  // 掩盖了未来新增类型的遗漏
}

else 分支是”逃生舱”,会掩盖未来新增子类的遗漏,应避免在密封类场景使用。


7. 常见陷阱与最佳实践

7.1 陷阱:when 用作语句而非表达式

// 陷阱:when 作为语句,不强制穷举
fun handle(state: State) {
    when (state) {
        is State.Loading -> showLoading()
        // 不报错,遗漏 Success 与 Error
    }
}

// 最佳实践:将 when 作为表达式
fun handle(state: State) {
    val ignored = when (state) {
        is State.Loading -> showLoading()
        is State.Success -> showSuccess(state.data)
        is State.Error -> showError(state.message)
    }
    // 编译器强制穷举
}

7.2 陷阱:滥用 else 分支

// 陷阱:else 分支掩盖遗漏
fun name(state: State): String = when (state) {
    is State.Loading -> "Loading"
    else -> "Unknown"  // 掩盖了 Success 与 Error 的遗漏
}

// 最佳实践:穷举所有分支,禁用 else
fun name(state: State): String = when (state) {
    is State.Loading -> "Loading"
    is State.Success -> "Success"
    is State.Error -> "Error"
}

7.3 陷阱:子类跨包(Kotlin 1.5 前)

// Kotlin 1.4 及以前:子类必须在同文件
// 文件:com/example/Shape.kt
package com.example
sealed class Shape

// 文件:com/example/other/Circle.kt
package com.example.other
class Circle(val radius: Double) : com.example.Shape()  // 编译错误!

// Kotlin 1.5+:子类可在同包任意文件
// 文件:com/example/Shape.kt
package com.example
sealed class Shape

// 文件:com/example/Circle.kt
package com.example  // 同包
class Circle(val radius: Double) : Shape()  // OK

7.4 陷阱:密封类构造器可见性

sealed class Result {
    // 默认 protected
    data class Success<T>(val value: T) : Result()

    // 错误:不能是 public
    data class Error public constructor(val message: String) : Result()  // 编译错误

    // 正确:private
    data class Loading private constructor(val time: Long) : Result() {
        companion object {
            fun create() = Loading(System.currentTimeMillis())
        }
    }
}

7.5 陷阱:object 子类用 is 检查

sealed class Result {
    object Loading : Result()
    data class Success<T>(val value: T) : Result()
}

fun handle(r: Result<Int>) = when (r) {
    is Result.Loading -> println("Loading")  // 可行,但非最佳
    is Result.Success -> println(r.value)
}

// 最佳实践:object 子类用直接引用(更精确)
fun handleBest(r: Result<Int>) = when (r) {
    Result.Loading -> println("Loading")  // 引用单例
    is Result.Success -> println(r.value)
}

7.6 陷阱:智能转换失效(var 变量)

sealed class State {
    data class Loading(val progress: Int) : State()
    object Idle : State()
}

class ViewModel {
    var state: State = State.Idle

    fun check() {
        if (state is State.Loading) {
            // state.progress 可能编译错误(var 可变,智能转换失效)
            // println(state.progress)
        }
    }
}

// 最佳实践:用 val 快照
fun check(state: State) {
    val snapshot = state
    if (snapshot is State.Loading) {
        println(snapshot.progress)  // 安全
    }
}

7.7 陷阱:嵌套密封类未深入穷举

sealed class AppState {
    sealed class Auth : AppState() {
        object Login : Auth()
        object Register : Auth()
    }
    object Main : AppState()
}

// 陷阱:未深入 Auth 子类
fun handle(s: AppState) = when (s) {
    is AppState.Auth -> showAuth()  // 未深入 Login 与 Register
    AppState.Main -> showMain()
}

// 最佳实践:深入嵌套子类
fun handleBest(s: AppState) = when (s) {
    AppState.Auth.Login -> showLogin()
    AppState.Auth.Register -> showRegister()
    AppState.Main -> showMain()
}

7.8 陷阱:Java 互操作下穷举检查失效

// Java 代码:可以"扩展"Kotlin 密封类(不推荐)
public class JavaState extends KotlinState {
    // Java 不识别 sealed,可继承但破坏穷举
}
// Kotlin 代码:若 JavaState 存在,when 不再穷举
fun handle(s: KotlinState) = when (s) {
    is KotlinState.A -> "A"
    is KotlinState.B -> "B"
    // 编译器警告:可能存在 Java 子类,需 else
    else -> "Unknown"
}

最佳实践:密封类不应被 Java 继承,可用 @JvmFieldinternal 限制。

7.9 陷阱:密封类与反射

sealed class Color {
    object RED : Color()
    object GREEN : Color()
    object BLUE : Color()
}

// 陷阱:用反射枚举子类
fun allColors(): List<Color> {
    return Color::class.sealedSubclasses.map { it.objectInstance as Color }
    // 依赖反射,性能差,且可能在混淆/反射受限环境下失效
}

最佳实践:用枚举或手动维护列表,避免反射。

7.10 陷阱:序列化类鉴别器冲突

@Serializable
sealed class Message {
    @Serializable @SerialName("text")
    data class Text(val content: String) : Message()

    @Serializable @SerialName("text")  // 陷阱:重复的 SerialName
    data class UpdateText(val id: String, val content: String) : Message()
}

// 反序列化时无法区分 Text 与 UpdateText

最佳实践:确保每个子类的 @SerialName 唯一。

7.11 陷阱:跨模块使用密封类

// module-a
sealed class ApiEvent

// module-b(依赖 module-a)
class ClickEvent : ApiEvent()  // 编译错误:子类必须与密封类同模块

最佳实践:密封类的所有子类必须与密封类在同一模块(同 Gradle 模块)。跨模块共享应使用接口而非密封类。

7.12 陷阱:when 分支顺序

sealed class Animal {
    data class Dog(val name: String) : Animal()
    object GenericDog : Animal()
}

// 陷阱:is Dog 在 GenericDog 之前,会捕获所有 Dog 实例(包括单例)
fun name(a: Animal) = when (a) {
    is Animal.Dog -> "Dog: ${a.name}"  // is Dog 匹配所有 Dog 实例
    Animal.GenericDog -> "Generic"     // 永远不会执行
}

// 最佳实践:单例分支在前
fun nameBest(a: Animal) = when (a) {
    Animal.GenericDog -> "Generic"     // 单例优先
    is Animal.Dog -> "Dog: ${a.name}"
}

7.13 陷阱:data classequals 误用

sealed class State {
    data class Loading(val progress: Int) : State()
    object Idle : State()
}

fun check(s: State) {
    // 陷阱:用 == 比较 data class 实例
    if (s == State.Loading(50)) { ... }  // 仅当 progress=50 时相等

    // 正确:用 is 检查类型
    if (s is State.Loading) { println(s.progress) }
}

7.14 陷阱:object 子类的全局状态

sealed class Counter {
    object GlobalCounter : Counter() {
        var count = 0  // 全局可变状态,线程不安全
        fun increment() { count++ }
    }
}

// 陷阱:GlobalCounter.count 是全局可变状态
fun increment() = Counter.GlobalCounter.increment()
// 在多线程下不安全

最佳实践object 子类应保持不可变,或用 AtomicInteger 等线程安全原语。

7.15 陷阱:过度使用密封类

// 陷阱:用密封类建模开放集合
sealed class UserType {
    object Admin : UserType()
    object User : UserType()
    object Guest : UserType()
    // 新增类型需修改密封类,违反开闭原则
}

最佳实践:仅当类型集合在编译期已知且稳定时使用密封类。开放集合应用接口或枚举 + 配置。


8. 工程实践

8.1 网络层封装(KMP)

// commonMain/NetworkResult.kt
package com.example.network

/**
 * 网络请求结果,跨平台共享。
 */
sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val code: Int, val message: String, val cause: Throwable? = null) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

/**
 * API 错误类型,跨平台共享。
 */
sealed class ApiError {
    data class Network(val cause: Throwable) : ApiError()
    data class Server(val code: Int, val message: String) : ApiError()
    data class Parse(val cause: Throwable) : ApiError()
    object Timeout : ApiError()
    object Unauthorized : ApiError()
}

/**
 * 跨平台网络请求封装。
 */
abstract class NetworkClient {
    abstract suspend fun <T> get(url: String, parser: (String) -> T): NetworkResult<T>
    abstract suspend fun <T> post(url: String, body: String, parser: (String) -> T): NetworkResult<T>
}

/**
 * 通用请求处理,复用 NetworkResult 与 ApiError。
 */
suspend fun <T> safeRequest(
    block: suspend () -> T
): NetworkResult<T> = try {
    NetworkResult.Success(block())
} catch (e: Exception) {
    val error = when (e) {
        is java.net.SocketTimeoutException -> ApiError.Timeout
        is java.net.UnknownHostException -> ApiError.Network(e)
        is HttpException -> ApiError.Server(e.code(), e.message())
        else -> ApiError.Network(e)
    }
    val (code, message) = when (error) {
        is ApiError.Network -> -1 to "Network error"
        is ApiError.Server -> error.code to error.message
        is ApiError.Parse -> -2 to "Parse error"
        ApiError.Timeout -> -3 to "Timeout"
        ApiError.Unauthorized -> 401 to "Unauthorized"
    }
    NetworkResult.Error(code, message, e)
}

8.2 MVI 架构完整实现

// MVI 完整实现示例

/**
 * 屏幕状态。
 */
sealed class ScreenState<out T> {
    object Idle : ScreenState<Nothing>()
    object Loading : ScreenState<Nothing>()
    data class Content<T>(val data: T) : ScreenState<T>()
    data class Error(val message: String, val retry: () -> Unit) : ScreenState<Nothing>()
}

/**
 * 用户意图。
 */
sealed class Wish {
    data class LoadUser(val id: String) : Wish()
    data class UpdateUserName(val name: String) : Wish()
    object Refresh : Wish()
    object Logout : Wish()
}

/**
 * 一次性副作用。
 */
sealed class Effect {
    data class ShowToast(val message: String) : Effect()
    data class Navigate(val route: String) : Effect()
    object ShowLogoutDialog : Effect()
}

/**
 * MVI Store,管理状态、意图、副作用。
 */
class Store<S, W, E>(
    initialState: S,
    private val reducer: (S, W) -> Pair<S, List<E>>
) {
    private val _state = MutableStateFlow(initialState)
    val state: StateFlow<S> = _state.asStateFlow()

    private val _effects = Channel<E>(Channel.BUFFERED)
    val effects: Flow<E> = _effects.receiveAsFlow()

    fun accept(wish: W) {
        val (newState, effects) = reducer(_state.value, wish)
        _state.value = newState
        effects.forEach { _effects.trySend(it) }
    }
}

/**
 * 用户页面 reducer,穷举所有 state × wish 组合。
 */
val userReducer: (ScreenState<User>, Wish) -> Pair<ScreenState<User>, List<Effect>> = { state, wish ->
    val effects = mutableListOf<Effect>()
    val newState: ScreenState<User> = when (state) {
        ScreenState.Idle -> when (wish) {
            is Wish.LoadUser -> ScreenState.Loading
            Wish.Refresh, is Wish.UpdateUserName, Wish.Logout -> state
        }
        ScreenState.Loading -> when (wish) {
            is Wish.LoadUser, Wish.Refresh, is Wish.UpdateUserName, Wish.Logout -> state  // 加载中忽略
        }
        is ScreenState.Content -> when (wish) {
            is Wish.LoadUser -> ScreenState.Loading
            is Wish.UpdateUserName -> {
                // 立即更新本地数据(乐观更新)
                ScreenState.Content(state.data.copy(name = wish.name))
            }
            Wish.Refresh -> ScreenState.Loading
            Wish.Logout -> {
                effects.add(Effect.ShowLogoutDialog)
                state
            }
        }
        is ScreenState.Error -> when (wish) {
            Wish.Refresh, is Wish.LoadUser -> ScreenState.Loading
            is Wish.UpdateUserName, Wish.Logout -> state
        }
    }
    newState to effects
}

8.3 事件溯源(Event Sourcing)

/**
 * 领域事件,用于事件溯源。
 */
sealed class AccountEvent {
    data class Created(val accountId: String, val owner: String, val initialBalance: Double) : AccountEvent()
    data class Deposited(val amount: Double, val timestamp: Long) : AccountEvent()
    data class Withdrawn(val amount: Double, val timestamp: Long) : AccountEvent()
    data class TransferredIn(val from: String, val amount: Double, val timestamp: Long) : AccountEvent()
    data class TransferredOut(val to: String, val amount: Double, val timestamp: Long) : AccountEvent()
    data class Closed(val reason: String, val timestamp: Long) : AccountEvent()
}

/**
 * 账户状态(聚合根)。
 */
sealed class AccountState {
    object NonExistent : AccountState()
    data class Active(val accountId: String, val owner: String, val balance: Double) : AccountState()
    data class Closed(val accountId: String, val owner: String, val closedAt: Long, val reason: String) : AccountState()
}

/**
 * 事件应用器,穷举所有 state × event 组合。
 */
fun apply(state: AccountState, event: AccountEvent): AccountState = when (state) {
    AccountState.NonExistent -> when (event) {
        is AccountEvent.Created -> AccountState.Active(event.accountId, event.owner, event.initialBalance)
        is AccountEvent.Deposited, is AccountEvent.Withdrawn,
        is AccountEvent.TransferredIn, is AccountEvent.TransferredOut,
        is AccountEvent.Closed -> error("Cannot apply $event to NonExistent account")
    }
    is AccountState.Active -> when (event) {
        is AccountEvent.Created -> error("Account already created")
        is AccountEvent.Deposited -> state.copy(balance = state.balance + event.amount)
        is AccountEvent.Withdrawn -> {
            require(state.balance >= event.amount) { "Insufficient balance" }
            state.copy(balance = state.balance - event.amount)
        }
        is AccountEvent.TransferredIn -> state.copy(balance = state.balance + event.amount)
        is AccountEvent.TransferredOut -> {
            require(state.balance >= event.amount) { "Insufficient balance" }
            state.copy(balance = state.balance - event.amount)
        }
        is AccountEvent.Closed -> AccountState.Closed(state.accountId, state.owner, event.timestamp, event.reason)
    }
    is AccountState.Closed -> when (event) {
        is AccountEvent.Created, is AccountEvent.Deposited, is AccountEvent.Withdrawn,
        is AccountEvent.TransferredIn, is AccountEvent.TransferredOut, is AccountEvent.Closed ->
            error("Cannot apply $event to Closed account")
    }
}

/**
 * 事件存储与回放。
 */
class EventSourcedAccount {
    private val events = mutableListOf<AccountEvent>()

    fun apply(event: AccountEvent) {
        events.add(event)
    }

    fun snapshot(): AccountState = events.fold(AccountState.NonExistent) { state, event ->
        apply(state, event)
    }
}

8.4 状态机引擎

/**
 * 通用状态机定义。
 */
sealed class StateMachine<S, E> {
    data class Transition<S, E>(
        val from: S,
        val event: E,
        val to: S,
        val action: (() -> Unit)? = null
    ) : StateMachine<S, E>()

    data class Terminal<S>(val state: S) : StateMachine<S, Nothing>()
}

/**
 * 订单状态机示例。
 */
sealed class OrderState {
    object Created : OrderState()
    object Paid : OrderState()
    object Shipped : OrderState()
    object Delivered : OrderState()
    object Cancelled : OrderState()
}

sealed class OrderEvent {
    object Pay : OrderEvent()
    object Ship : OrderEvent()
    object Deliver : OrderEvent()
    data class Cancel(val reason: String) : OrderEvent()
}

/**
 * 状态机配置。
 */
val orderTransitions = listOf(
    StateMachine.Transition(OrderState.Created, OrderEvent.Pay, OrderState.Paid),
    StateMachine.Transition(OrderState.Created, OrderEvent.Cancel(""), OrderState.Cancelled),
    StateMachine.Transition(OrderState.Paid, OrderEvent.Ship, OrderState.Shipped),
    StateMachine.Transition(OrderState.Paid, OrderEvent.Cancel(""), OrderState.Cancelled),
    StateMachine.Transition(OrderState.Shipped, OrderEvent.Deliver, OrderState.Delivered),
    StateMachine.Transition(OrderState.Delivered, null, OrderState.Delivered)  // 终态
)

/**
 * 状态机引擎。
 */
class StateMachineEngine<S, E>(transitions: List<StateMachine<S, *>>) {
    private val table: Map<Pair<S, E>, S> = buildTransitionTable(transitions)

    @Suppress("UNCHECKED_CAST")
    fun transition(current: S, event: E): S {
        return table[current to event] ?: error("No transition from $current on $event")
    }

    private fun buildTransitionTable(transitions: List<StateMachine<S, *>>): Map<Pair<S, E>, S> {
        val map = mutableMapOf<Pair<S, E>, S>()
        transitions.forEach { t ->
            if (t is StateMachine.Transition<S, *>) {
                @Suppress("UNCHECKED_CAST")
                map[t.from to (t.event as E)] = t.to
            }
        }
        return map
    }
}

8.5 前端 UI 组件 props 建模

/**
 * React/Vue 风格的组件 props,用密封类建模变体。
 */
sealed class ButtonVariant {
    object Primary : ButtonVariant()
    object Secondary : ButtonVariant()
    object Danger : ButtonVariant()
    object Ghost : ButtonVariant()
    data class Custom(val backgroundColor: String, val textColor: String) : ButtonVariant()
}

sealed class ButtonSize {
    object Small : ButtonSize()
    object Medium : ButtonSize()
    object Large : ButtonSize()
    data class Custom(val padding: Int, val fontSize: Int) : ButtonSize()
}

data class ButtonProps(
    val label: String,
    val variant: ButtonVariant = ButtonVariant.Primary,
    val size: ButtonSize = ButtonSize.Medium,
    val onClick: () -> Unit = {},
    val disabled: Boolean = false
)

fun renderButton(props: ButtonProps): String {
    val variantStyle = when (props.variant) {
        ButtonVariant.Primary -> "background: blue; color: white;"
        ButtonVariant.Secondary -> "background: gray; color: white;"
        ButtonVariant.Danger -> "background: red; color: white;"
        ButtonVariant.Ghost -> "background: transparent; color: inherit;"
        is ButtonVariant.Custom -> "background: ${props.variant.backgroundColor}; color: ${props.variant.textColor};"
    }
    val sizeStyle = when (props.size) {
        ButtonSize.Small -> "padding: 4px 8px; font-size: 12px;"
        ButtonSize.Medium -> "padding: 8px 16px; font-size: 14px;"
        ButtonSize.Large -> "padding: 12px 24px; font-size: 16px;"
        is ButtonSize.Custom -> "padding: ${props.size.padding}px; font-size: ${props.size.fontSize}px;"
    }
    return """<button style="$variantStyle $sizeStyle" ${if (props.disabled) "disabled" else ""}>${props.label}</button>"""
}

8.6 KMP 跨平台 UI 状态共享

// commonMain/UiState.kt
package com.example.ui

sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String, val onRetry: (() -> Unit)? = null) : UiState<Nothing>()
    object Empty : UiState<Nothing>()
}

// commonMain/UserRepository.kt
package com.example.data

class UserRepository(private val api: ApiService) {
    suspend fun fetchUser(id: String): UiState<User> = try {
        val user = api.getUser(id)
        if (user != null) UiState.Success(user)
        else UiState.Empty
    } catch (e: Exception) {
        UiState.Error(e.message ?: "Unknown error")
    }
}

// androidMain/AndroidUserScreen.kt
package com.example.ui

@Composable
fun UserScreen(viewModel: UserViewModel) {
    val state by viewModel.state.collectAsState()
    when (val s = state) {
        UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> Text("User: ${s.data.name}")
        is UiState.Error -> Column {
            Text("Error: ${s.message}")
            s.onRetry?.let { Button(onClick = it) { Text("Retry") } }
        }
        UiState.Empty -> Text("No user found")
    }
}

// iosMain/IosUserScreen.kt
package com.example.ui

class IosUserScreen(private val viewModel: UserViewModel) {
    func render() -> UIView {
        switch viewModel.state.value {
        case .loading: return UIActivityIndicatorView()
        case .success(let user): return UILabel(text: "User: \(user.name)")
        case .error(let message): return UILabel(text: "Error: \(message)")
        case .empty: return UILabel(text: "No user found")
        }
    }
}

8.7 错误处理与错误传播

/**
 * 应用错误类型,用密封类建模所有可能的错误。
 */
sealed class AppError {
    data class Network(val code: Int, val message: String) : AppError()
    data class Validation(val field: String, val reason: String) : AppError()
    data class Database(val query: String, val cause: Throwable) : AppError()
    data class Business(val rule: String, val context: Map<String, Any>) : AppError()
    object Unauthorized : AppError()
    object Forbidden : AppError()
    object NotFound : AppError()
    object Timeout : AppError()
    data class Unknown(val cause: Throwable) : AppError()
}

/**
 * 错误转用户友好消息。
 */
fun AppError.toUserMessage(): String = when (this) {
    is AppError.Network -> "网络错误(${code}):$message"
    is AppError.Validation -> "字段 $field 校验失败:$reason"
    is AppError.Database -> "数据库错误,请稍后重试"
    is AppError.Business -> "操作失败:$rule"
    AppError.Unauthorized -> "未登录或登录已过期"
    AppError.Forbidden -> "无权限访问"
    AppError.NotFound -> "资源不存在"
    AppError.Timeout -> "请求超时,请检查网络"
    is AppError.Unknown -> "未知错误,请联系客服"
}

/**
 * 错误转 HTTP 状态码。
 */
fun AppError.toHttpCode(): Int = when (this) {
    is AppError.Network -> 503
    is AppError.Validation -> 400
    is AppError.Database -> 500
    is AppError.Business -> 422
    AppError.Unauthorized -> 401
    AppError.Forbidden -> 403
    AppError.NotFound -> 404
    AppError.Timeout -> 504
    is AppError.Unknown -> 500
}

/**
 * 错误日志记录,穷举所有错误类型。
 */
fun AppError.log(logger: Logger) {
    when (this) {
        is AppError.Network -> logger.warn("Network error: $code $message")
        is AppError.Validation -> logger.info("Validation: $field - $reason")
        is AppError.Database -> logger.error("Database error: $query", cause)
        is AppError.Business -> logger.warn("Business rule violated: $rule, context: $context")
        AppError.Unauthorized -> logger.info("Unauthorized access")
        AppError.Forbidden -> logger.warn("Forbidden access")
        AppError.NotFound -> logger.info("Not found")
        AppError.Timeout -> logger.warn("Timeout")
        is AppError.Unknown -> logger.error("Unknown error", cause)
    }
}

8.8 配置管理

/**
 * 配置值类型,用密封类支持异构配置。
 */
sealed class ConfigValue {
    data class StringValue(val value: String) : ConfigValue()
    data class IntValue(val value: Int) : ConfigValue()
    data class DoubleValue(val value: Double) : ConfigValue()
    data class BooleanValue(val value: Boolean) : ConfigValue()
    data class ListValue(val values: List<ConfigValue>) : ConfigValue()
    data class MapValue(val entries: Map<String, ConfigValue>) : ConfigValue()
    object NullValue : ConfigValue()
}

class Config(private val values: Map<String, ConfigValue>) {
    fun getString(key: String): String? = (values[key] as? ConfigValue.StringValue)?.value
    fun getInt(key: String): Int? = (values[key] as? ConfigValue.IntValue)?.value
    fun getDouble(key: String): Double? = (values[key] as? ConfigValue.DoubleValue)?.value
    fun getBoolean(key: String): Boolean? = (values[key] as? ConfigValue.BooleanValue)?.value
    fun getList(key: String): List<ConfigValue>? = (values[key] as? ConfigValue.ListValue)?.values
    fun getMap(key: String): Map<String, ConfigValue>? = (values[key] as? ConfigValue.MapValue)?.entries
}

/**
 * 配置序列化为 JSON。
 */
fun ConfigValue.toJson(): String = when (this) {
    is ConfigValue.StringValue -> "\"$value\""
    is ConfigValue.IntValue -> value.toString()
    is ConfigValue.DoubleValue -> value.toString()
    is ConfigValue.BooleanValue -> value.toString()
    is ConfigValue.ListValue -> values.joinToString(",", "[", "]") { it.toJson() }
    is ConfigValue.MapValue -> entries.entries.joinToString(",", "{", "}") { "\"${it.key}\":${it.value.toJson()}" }
    ConfigValue.NullValue -> "null"
}

8.9 表单验证

sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val errors: List<String>) : ValidationResult()

    fun isValid(): Boolean = this is Valid
    infix fun and(other: ValidationResult): ValidationResult = when {
        this is Valid && other is Valid -> Valid
        this is Invalid && other is Invalid -> Invalid(this.errors + other.errors)
        this is Invalid -> this
        else -> other
    }
}

sealed class FieldRule {
    data class MinLength(val length: Int) : FieldRule()
    data class MaxLength(val length: Int) : FieldRule()
    data class Regex(val pattern: String) : FieldRule()
    data class Custom(val predicate: (String) -> Boolean, val message: String) : FieldRule()
    object Required : FieldRule()
    object Email : FieldRule()
}

fun validate(value: String, rules: List<FieldRule>): ValidationResult {
    val errors = mutableListOf<String>()
    rules.forEach { rule ->
        when (rule) {
            is FieldRule.MinLength -> if (value.length < rule.length) errors.add("Min length ${rule.length}")
            is FieldRule.MaxLength -> if (value.length > rule.length) errors.add("Max length ${rule.length}")
            is FieldRule.Regex -> if (!value.matches(Regex.toRegexSafe(rule.pattern))) errors.add("Invalid format")
            is FieldRule.Custom -> if (!rule.predicate(value)) errors.add(rule.message)
            FieldRule.Required -> if (value.isBlank()) errors.add("Required")
            FieldRule.Email -> if (!value.matches(EMAIL_REGEX)) errors.add("Invalid email")
        }
    }
    return if (errors.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(errors)
}

private val EMAIL_REGEX = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\$")

private fun Regex.Companion.toRegexSafe(pattern: String): kotlin.text.Regex =
    kotlin.text.Regex(pattern)

8.10 API 响应统一封装

/**
 * 统一 API 响应封装。
 */
@Serializable
sealed class ApiResponse<out T> {
    @Serializable @SerialName("success")
    data class Success<T>(val data: T, val timestamp: Long = System.currentTimeMillis()) : ApiResponse<T>()

    @Serializable @SerialName("error")
    data class Error(val code: String, val message: String, val details: Map<String, String> = emptyMap()) : ApiResponse<Nothing>()

    @Serializable @SerialName("paginated")
    data class Paginated<T>(
        val data: List<T>,
        val page: Int,
        val pageSize: Int,
        val total: Long
    ) : ApiResponse<List<T>>()
}

/**
 * Spring Boot 控制器统一返回 ApiResponse。
 */
@RestController
class UserController {
    @GetMapping("/users/{id}")
    suspend fun getUser(@PathVariable id: String): ApiResponse<User> = try {
        val user = userService.findById(id) ?: return ApiResponse.Error("NOT_FOUND", "User not found")
        ApiResponse.Success(user)
    } catch (e: Exception) {
        ApiResponse.Error("INTERNAL_ERROR", e.message ?: "Unknown error")
    }

    @GetMapping("/users")
    suspend fun listUsers(
        @RequestParam page: Int = 0,
        @RequestParam size: Int = 20
    ): ApiResponse<List<User>> {
        val result = userService.findAll(page, size)
        return ApiResponse.Paginated(result.content, page, size, result.totalElements)
    }
}

/**
 * 客户端统一处理 ApiResponse。
 */
fun <T> handleApiResponse(response: ApiResponse<T>): T? = when (response) {
    is ApiResponse.Success -> response.data
    is ApiResponse.Error -> {
        println("Error ${response.code}: ${response.message}")
        null
    }
    is ApiResponse.Paginated -> response.data.firstOrNull()
}

9. 案例研究

9.1 案例一:Kotlin 标准库 Result

Kotlin 标准库的 Result<T> 类(虽然不是密封类,但设计思想类似):

// 标准库源码(简化)
@JvmInline
value class Result<out T> internal constructor(internal val value: Any?) {
    val isSuccess: Boolean get() = value !is Failure
    val isFailure: Boolean get() = value is Failure

    fun getOrNull(): T? = if (isSuccess) value as T else null
    fun exceptionOrNull(): Throwable? = if (isFailure) (value as Failure).exception else null

    inline fun <R> map(transform: (T) -> R): Result<R> =
        if (isSuccess) success(transform(value as T)) else this as Result<R>

    companion object {
        fun <T> success(value: T): Result<T> = Result(value)
        fun <T> failure(exception: Throwable): Result<T> = Result(Failure(exception))
    }

    private class Failure(val exception: Throwable)
}

为什么 Result 不是密封类

  1. Resultvalue class(内联类),基于性能考虑不能用密封类。
  2. 密封类在 JVM 上需要 @Metadata,而 value class 追求零开销。
  3. 设计哲学不同:Result 是”操作结果”,不是”类型分类”。

9.2 案例二:kotlinx.coroutinesDeferred 状态

Deferred 内部状态用密封类管理:

// 简化版
sealed class JobState {
    object New : JobState()
    object Active : JobState()
    object Completing : JobState()
    data class Completed<T>(val value: T) : JobState()
    data class Failed(val exception: Throwable) : JobState()
    object Cancelled : JobState()
}

class Deferred<T> {
    private var state: JobState = JobState.New

    fun complete(value: T): Boolean = synchronized(this) {
        if (state is JobState.New || state is JobState.Active) {
            state = JobState.Completed(value)
            true
        } else false
    }

    fun cancel(cause: Throwable? = null): Boolean = synchronized(this) {
        if (state !is JobState.Completed<*> && state !is JobState.Failed && state !is JobState.Cancelled) {
            state = if (cause != null) JobState.Failed(cause) else JobState.Cancelled
            true
        } else false
    }

    fun await(): T = synchronized(this) {
        when (val s = state) {
            is JobState.Completed<*> -> s.value as T
            is JobState.Failed -> throw s.exception
            JobState.Cancelled -> throw CancellationException()
            else -> throw IllegalStateException("Not completed: $s")
        }
    }
}

9.3 案例三:Jetpack Compose 的 Modifier

Modifier 链式构建,内部用密封类表示链节点:

// 简化版
sealed class Modifier {
    object Empty : Modifier()

    data class Element(val key: String, val value: Any) : Modifier()

    data class Combined(val left: Modifier, val right: Modifier) : Modifier()

    fun then(other: Modifier): Modifier = if (other is Empty) this else Combined(this, other)

    fun fold(initial: Any, operation: (Any, Element) -> Any): Any {
        var acc = initial
        when (this) {
            Empty -> {}
            is Element -> operation(acc, this)
            is Combined -> {
                acc = left.fold(acc, operation)
                acc = right.fold(acc, operation)
            }
        }
        return acc
    }
}

9.4 案例四:Android ViewModel 的 UI 状态

/**
 * 屏幕状态,使用密封类表示四态。
 */
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String, val retry: () -> Unit) : UiState<Nothing>()
    object Empty : UiState<Nothing>()
}

class UserViewModel(
    private val repository: UserRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _state = MutableStateFlow<UiState<User>>(UiState.Loading)
    val state: StateFlow<UiState<User>> = _state.asStateFlow()

    init {
        loadUser()
    }

    fun loadUser() {
        viewModelScope.launch {
            _state.value = UiState.Loading
            _state.value = try {
                val user = repository.fetchUser()
                if (user != null) UiState.Success(user)
                else UiState.Empty
            } catch (e: Exception) {
                UiState.Error(e.message ?: "Unknown error") { loadUser() }
            }
        }
    }
}

// 在 Compose 中使用
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val state by viewModel.state.collectAsState()
    when (val s = state) {
        UiState.Loading -> LoadingIndicator()
        is UiState.Success -> UserContent(s.data)
        is UiState.Error -> ErrorView(s.message, s.retry)
        UiState.Empty -> EmptyView()
    }
}

9.5 案例五:Arrow-ktEither 类型

Arrow 库的 Either<L, R> 用密封类实现:

// Arrow-kt 源码(简化)
sealed class Either<out A, out B> {
    data class Left<out A>(val value: A) : Either<A, Nothing>()
    data class Right<out B>(val value: B) : Either<Nothing, B>()

    inline fun <C> map(f: (B) -> C): Either<A, C> = when (this) {
        is Left -> this
        is Right -> Right(f(value))
    }

    inline fun <C> flatMap(f: (B) -> Either<A, C>): Either<A, C> = when (this) {
        is Left -> this
        is Right -> f(value)
    }

    inline fun getOrElse(default: (A) -> B): B = when (this) {
        is Left -> default(value)
        is Right -> value
    }
}

// 使用示例
fun divide(a: Int, b: Int): Either<String, Int> =
    if (b == 0) Either.Left("Division by zero")
    else Either.Right(a / b)

fun main() {
    val result = divide(10, 0)
        .map { it * 2 }
        .getOrElse { "Error: $it" }
    println(result)  // Error: Division by zero
}

9.6 案例六:kotlinx.serialization 的多态序列化

@Serializable
sealed class Animal {
    @Serializable
    @SerialName("dog")
    data class Dog(val name: String, val breed: String) : Animal()

    @Serializable
    @SerialName("cat")
    data class Cat(val name: String, val indoor: Boolean) : Animal()

    @Serializable
    @SerialName("bird")
    data class Bird(val species: String, val canFly: Boolean) : Animal()
}

fun main() {
    val json = Json { ignoreUnknownKeys = true }

    // 序列化
    val animals: List<Animal> = listOf(
        Animal.Dog("Buddy", "Golden Retriever"),
        Animal.Cat("Whiskers", true),
        Animal.Bird("Parrot", true)
    )
    val jsonString = json.encodeToString(animals)
    println(jsonString)
    // [{"type":"dog","name":"Buddy","breed":"Golden Retriever"},...]

    // 反序列化
    val decoded = json.decodeFromString<List<Animal>>(jsonString)
    decoded.forEach { animal ->
        val description = when (animal) {
            is Animal.Dog -> "Dog: ${animal.name} (${animal.breed})"
            is Animal.Cat -> "Cat: ${animal.name} (${if (animal.indoor) "indoor" else "outdoor"})"
            is Animal.Bird -> "Bird: ${animal.species} (${if (animal.canFly) "fly" else "no-fly"})"
        }
        println(description)
    }
}

9.7 案例七:Gradle Kotlin DSL 中的密封类

// 假设 Gradle 插件用密封类定义配置
sealed class AndroidConfig {
    data class MinSdk(val version: Int) : AndroidConfig()
    data class TargetSdk(val version: Int) : AndroidConfig()
    data class ApplicationId(val id: String) : AndroidConfig()
    data class VersionCode(val code: Int) : AndroidConfig()
    data class VersionName(val name: String) : AndroidConfig()
}

class AndroidConfigBuilder {
    private val configs = mutableListOf<AndroidConfig>()

    fun minSdk(version: Int) { configs.add(AndroidConfig.MinSdk(version)) }
    fun targetSdk(version: Int) { configs.add(AndroidConfig.TargetSdk(version)) }
    fun applicationId(id: String) { configs.add(AndroidConfig.ApplicationId(id)) }
    fun versionCode(code: Int) { configs.add(AndroidConfig.VersionCode(code)) }
    fun versionName(name: String) { configs.add(AndroidConfig.VersionName(name)) }

    fun build(): List<AndroidConfig> = configs.toList()
}

// build.gradle.kts
android {
    val configs = AndroidConfigBuilder().apply {
        minSdk(21)
        targetSdk(34)
        applicationId("com.example.app")
        versionCode(1)
        versionName("1.0.0")
    }.build()

    configs.forEach { config ->
        when (config) {
            is AndroidConfig.MinSdk -> minSdk = config.version
            is AndroidConfig.TargetSdk -> targetSdk = config.version
            is AndroidConfig.ApplicationId -> applicationId = config.id
            is AndroidConfig.VersionCode -> versionCode = config.code
            is AndroidConfig.VersionName -> versionName = config.name
        }
    }
}

9.8 案例八:Android 导航事件

/**
 * 导航事件,用密封类建模所有可能的导航目标。
 */
sealed class NavigationEvent {
    data class ToUserDetail(val userId: String) : NavigationEvent()
    data class ToSettings(val section: String = "general") : NavigationEvent()
    object Back : NavigationEvent()
    object BackToRoot : NavigationEvent()
    data class DeepLink(val uri: String, val extras: Map<String, String> = emptyMap()) : NavigationEvent()
    data class Replace(val route: String) : NavigationEvent()
}

class NavigationManager(private val navController: NavController) {
    fun navigate(event: NavigationEvent) {
        when (event) {
            is NavigationEvent.ToUserDetail -> {
                navController.navigate("user/${event.userId}")
            }
            is NavigationEvent.ToSettings -> {
                navController.navigate("settings/${event.section}")
            }
            NavigationEvent.Back -> navController.popBackStack()
            NavigationEvent.BackToRoot -> {
                navController.popBackStack(
                    navController.graph.findStartDestination().id,
                    inclusive = false,
                    saveState = true
                )
            }
            is NavigationEvent.DeepLink -> {
                val extras = event.extras.fold(
                    NavOptions.Builder().build()
                ) { builder, (key, value) ->
                    builder
                }
                navController.navigate(Uri.parse(event.uri))
            }
            is NavigationEvent.Replace -> {
                navController.navigate(event.route) {
                    popUpTo(navController.graph.startDestinationId) { inclusive = true }
                }
            }
        }
    }
}

9.9 案例九:Ktor 请求与响应建模

/**
 * Ktor 请求与响应类型,用密封类建模。
 */
sealed class Request<out T> {
    data class Get(val url: String, val params: Map<String, String> = emptyMap()) : Request<Nothing>()
    data class Post(val url: String, val body: String) : Request<Nothing>()
    data class Put(val url: String, val body: String) : Request<Nothing>()
    data class Delete(val url: String) : Request<Nothing>()
}

sealed class Response<out T> {
    data class Success<T>(val data: T, val statusCode: Int = 200) : Response<T>()
    data class Error(val statusCode: Int, val message: String) : Response<Nothing>()
    object Loading : Response<Nothing>()
}

class KtorClient(private val httpClient: HttpClient) {
    suspend fun <T> execute(request: Request<T>, parser: (String) -> T): Response<T> = try {
        val response: String = when (request) {
            is Request.Get -> httpClient.get(request.url) { request.params.forEach { (k, v) -> parameter(k, v) } }
            is Request.Post -> httpClient.post(request.url) { body = TextContent(request.body, ContentType.Application.Json) }
            is Request.Put -> httpClient.put(request.url) { body = TextContent(request.body, ContentType.Application.Json) }
            is Request.Delete -> httpClient.delete(request.url)
        }.bodyAsText()
        Response.Success(parser(response))
    } catch (e: ClientRequestException) {
        Response.Error(e.response.status.value, e.message ?: "Client error")
    } catch (e: ServerResponseException) {
        Response.Error(e.response.status.value, "Server error")
    } catch (e: Exception) {
        Response.Error(-1, e.message ?: "Unknown error")
    }
}

9.10 案例十:KMP 项目中的共享数据模型

// commonMain/domain/Account.kt
package com.example.domain

/**
 * 账户领域模型,跨平台共享。
 */
sealed class Account {
    data class Active(
        val id: String,
        val owner: String,
        val balance: Double,
        val currency: String
    ) : Account()

    data class Frozen(
        val id: String,
        val owner: String,
        val balance: Double,
        val frozenAt: Long,
        val reason: String
    ) : Account()

    data class Closed(
        val id: String,
        val owner: String,
        val closedAt: Long,
        val finalBalance: Double
    ) : Account()
}

/**
 * 账户操作结果,跨平台共享。
 */
sealed class AccountOperationResult {
    data class Success(val newAccount: Account) : AccountOperationResult()
    data class InsufficientBalance(val required: Double, val actual: Double) : AccountOperationResult()
    data class AccountFrozen(val account: Account.Frozen) : AccountOperationResult()
    data class AccountClosed(val account: Account.Closed) : AccountOperationResult()
    object NotFound : AccountOperationResult()
    data class ValidationError(val field: String, val reason: String) : AccountOperationResult()
}

/**
 * 账户服务,跨平台共享业务逻辑。
 */
class AccountService {
    fun deposit(account: Account, amount: Double): AccountOperationResult = when (account) {
        is Account.Active -> AccountOperationResult.Success(
            account.copy(balance = account.balance + amount)
        )
        is Account.Frozen -> AccountOperationResult.AccountFrozen(account)
        is Account.Closed -> AccountOperationResult.AccountClosed(account)
    }

    fun withdraw(account: Account, amount: Double): AccountOperationResult = when (account) {
        is Account.Active -> when {
            account.balance < amount -> AccountOperationResult.InsufficientBalance(amount, account.balance)
            else -> AccountOperationResult.Success(
                account.copy(balance = account.balance - amount)
            )
        }
        is Account.Frozen -> AccountOperationResult.AccountFrozen(account)
        is Account.Closed -> AccountOperationResult.AccountClosed(account)
    }

    fun close(account: Account, reason: String): AccountOperationResult = when (account) {
        is Account.Active -> AccountOperationResult.Success(
            Account.Closed(account.id, account.owner, System.currentTimeMillis(), account.balance)
        )
        is Account.Frozen -> AccountOperationResult.Success(
            Account.Closed(account.id, account.owner, System.currentTimeMillis(), account.balance)
        )
        is Account.Closed -> AccountOperationResult.AccountClosed(account)
    }
}

10. 习题

10.1 基础题

题目 1:用密封类建模”二叉树”,节点可以是叶子(携带 Int 值)或内部节点(携带左右子树)。实现 sum() 函数递归求和。

参考答案

sealed class Tree {
    data class Leaf(val value: Int) : Tree()
    data class Node(val left: Tree, val right: Tree) : Tree()
}

fun sum(tree: Tree): Int = when (tree) {
    is Tree.Leaf -> tree.value
    is Tree.Node -> sum(tree.left) + sum(tree.right)
}

fun main() {
    val tree = Tree.Node(
        Tree.Leaf(1),
        Tree.Node(Tree.Leaf(2), Tree.Leaf(3))
    )
    println(sum(tree))  // 6
}

题目 2:用密封接口建模”可绘制”能力,让 CircleSquareColor(枚举)都实现它。

参考答案

sealed interface Drawable {
    fun draw(): String
}

data class Circle(val radius: Double) : Drawable {
    override fun draw() = "Circle(r=$radius)"
}

data class Square(val side: Double) : Drawable {
    override fun draw() = "Square(s=$side)"
}

enum class Color(override val draw: () -> String) : Drawable {
    RED({ "Red" }),
    GREEN({ "Green" }),
    BLUE({ "Blue" })
}

10.2 中级题

题目 3:实现 Result<T, E> 类型,提供 mapflatMapgetOrElsefold 方法,并使用密封类穷举所有情况。

参考答案:见 5.12 节代码示例。

题目 4:用密封类建模”表单状态”,覆盖 EmptyPartiallyFilledCompleteInvalidSubmittingSubmitted 六态,并实现 update(state, event) 函数穷举所有 state × event 组合。

参考答案:见 5.11 节代码示例。

10.3 高级题

题目 5:用密封类实现一个 JSON 解析器,支持 ObjectArrayStringNumberBooleanNull 六种节点。实现 parse(json: String): Jsonstringify(json: Json): String

参考答案:见 5.4 节代码示例。

题目 6:用密封类实现一个有限状态机(FSM),支持订单状态:CreatedPaidShippedDelivered(或 Cancelled)。实现 transition(state, event) 函数穷举所有合法转换。

参考答案:见 5.6 节代码示例。

10.4 设计题

题目 7:设计一个 KMP 项目的网络层,使用密封类建模 NetworkResultApiError,跨平台共享业务逻辑。

参考答案:见 8.1 节代码示例。

题目 8:设计一个 MVI 架构的 Store,使用密封类建模 StateWishEffect,并实现 reducer 函数。

参考答案:见 8.2 节代码示例。

10.5 分析题

题目 9:分析以下代码的穷举检查是否通过,并说明原因。

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>()
}

fun handle(r: Result<Int>) {
    when (r) {
        is Result.Success -> println(r.value)
        Result.Loading -> println("Loading")
        // 是否通过穷举检查?
    }
}

参考答案

不通过。when 作为语句(无返回值)时不强制穷举,但若改为表达式则报错:

fun handle(r: Result<Int>) {
    val ignored = when (r) {  // 此时强制穷举
        is Result.Success -> println(r.value)
        Result.Loading -> println("Loading")
        // 缺少 Error 分支,编译错误
    }
}

正确做法:

fun handle(r: Result<Int>) = when (r) {
    is Result.Success -> println(r.value)
    Result.Loading -> println("Loading")
    is Result.Error -> println("Error: ${r.message}")  // 补充
}

题目 10:分析 Kotlin 1.4 与 1.5 在密封类子类位置上的差异,并说明对项目结构的影响。

参考答案

  • Kotlin 1.4 及以前:子类必须与密封类在同一文件,大型项目难以拆分。
  • Kotlin 1.5+:子类可在同包任意文件,允许按功能拆分文件:
com/example/shapes/
├── Shape.kt          // sealed class Shape
├── Circle.kt         // class Circle : Shape()
├── Square.kt         // class Square : Shape()
├── Triangle.kt       // class Triangle : Shape()
└── Pentagon.kt       // class Pentagon : Shape()

影响:项目结构更灵活,但子类仍需与密封类同模块(同 Gradle 模块)。

10.6 综合题

题目 11:用密封类实现一个”日志级别”系统,包含 TRACEDEBUGINFOWARNERRORFATAL 六个级别。每个级别携带时间戳与消息,FATAL 额外携带异常堆栈,INFO 携带标签列表。实现 format() 方法返回统一格式化字符串,并通过 when 穷举处理。要求支持从 JSON 反序列化(使用 kotlinx.serialization)。

参考答案

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

@Serializable
sealed class LogLevel {
    abstract val timestamp: Long
    abstract val message: String

    abstract fun format(): String

    @Serializable
    @SerialName("trace")
    data class Trace(
        override val timestamp: Long,
        override val message: String
    ) : LogLevel() {
        override fun format(): String = "[TRACE] $timestamp $message"
    }

    @Serializable
    @SerialName("debug")
    data class Debug(
        override val timestamp: Long,
        override val message: String
    ) : LogLevel() {
        override fun format(): String = "[DEBUG] $timestamp $message"
    }

    @Serializable
    @SerialName("info")
    data class Info(
        override val timestamp: Long,
        override val message: String,
        val tags: List<String> = emptyList()
    ) : LogLevel() {
        override fun format(): String = "[INFO] $timestamp $message tags=${tags.joinToString(",")}"
    }

    @Serializable
    @SerialName("warn")
    data class Warn(
        override val timestamp: Long,
        override val message: String
    ) : LogLevel() {
        override fun format(): String = "[WARN] $timestamp $message"
    }

    @Serializable
    @SerialName("error")
    data class Error(
        override val timestamp: Long,
        override val message: String
    ) : LogLevel() {
        override fun format(): String = "[ERROR] $timestamp $message"
    }

    @Serializable
    @SerialName("fatal")
    data class Fatal(
        override val timestamp: Long,
        override val message: String,
        val stackTrace: List<String>
    ) : LogLevel() {
        override fun format(): String =
            "[FATAL] $timestamp $message\n${stackTrace.joinToString("\n") { "  at $it" }}"
    }
}

fun handleLog(level: LogLevel): Unit = when (level) {
    is LogLevel.Trace -> println("Verbose: ${level.message}")
    is LogLevel.Debug -> println("Diagnostic: ${level.message}")
    is LogLevel.Info -> println("Informational with tags: ${level.tags}")
    is LogLevel.Warn -> println("Warning: ${level.message}")
    is LogLevel.Error -> println("Error occurred: ${level.message}")
    is LogLevel.Fatal -> {
        println("Fatal error: ${level.message}")
        level.stackTrace.forEach(::println)
    }
}

fun main() {
    val json = """
        {"type":"info","timestamp":1700000000,"message":"Server started","tags":["boot","system"]}
    """.trimIndent()
    val parsed = Json.decodeFromString<LogLevel>(json)
    println(parsed.format())
    handleLog(parsed)
}

要点解析

  1. 密封类 LogLevel 定义抽象 format(),所有子类必须实现,符合开闭原则。
  2. @SerialName 为每个子类指定 JSON 类鉴别器,多态序列化时区分类型。
  3. when 表达式穷举所有 6 个子类,新增级别时编译失败,保证完整性。
  4. 智能转换使每个分支可直接访问子类特有属性(如 level.tagslevel.stackTrace)。

题目 12:设计一个基于密封类的”有限状态机”(FSM),用于建模订单生命周期。状态包括:CreatedPaidShippedDeliveredCancelled。事件包括:PayShipDeliverCancel。要求:

  1. 定义 State 密封类与 Event 密封类。
  2. 实现 transition(state: State, event: Event): State 函数,用 when 穷举所有合法状态-事件组合,非法组合抛出 IllegalStateException
  3. 实现状态机不允许在 Delivered 后取消。
  4. 添加 eventLog: List<Event> 记录事件历史。

参考答案

sealed class State {
    object Created : State()
    object Paid : State()
    object Shipped : State()
    object Delivered : State()
    object Cancelled : State()
}

sealed class Event {
    object Pay : Event()
    object Ship : Event()
    object Deliver : Event()
    object Cancel : Event()
}

class OrderStateMachine {
    private var state: State = State.Created
    private val eventLog: MutableList<Event> = mutableListOf()

    fun process(event: Event): State {
        val next = transition(state, event)
        eventLog.add(event)
        state = next
        return next
    }

    fun transition(state: State, event: Event): State = when (state) {
        State.Created -> when (event) {
            Event.Pay -> State.Paid
            Event.Cancel -> State.Cancelled
            Event.Ship, Event.Deliver ->
                error("Invalid: cannot ship/deliver before payment")
        }
        State.Paid -> when (event) {
            Event.Ship -> State.Shipped
            Event.Cancel -> State.Cancelled
            Event.Pay -> error("Already paid")
            Event.Deliver -> error("Cannot deliver before shipping")
        }
        State.Shipped -> when (event) {
            Event.Deliver -> State.Delivered
            Event.Cancel -> error("Cannot cancel after shipping")
            Event.Pay -> error("Already paid")
            Event.Ship -> error("Already shipped")
        }
        State.Delivered -> when (event) {
            Event.Pay, Event.Ship, Event.Deliver, Event.Cancel ->
                error("Order already delivered, no further transitions")
        }
        State.Cancelled -> when (event) {
            Event.Pay, Event.Ship, Event.Deliver, Event.Cancel ->
                error("Order already cancelled, no further transitions")
        }
    }

    fun current(): State = state
    fun history(): List<Event> = eventLog.toList()
}

fun main() {
    val sm = OrderStateMachine()
    sm.process(Event.Pay)
    sm.process(Event.Ship)
    sm.process(Event.Deliver)
    println(sm.current())  // Delivered
    println(sm.history())  // [Pay, Ship, Deliver]
}

要点解析

  1. 双重 when 穷举:外层覆盖所有 State 子类,内层覆盖所有 Event 子类。
  2. 新增 StateEvent 时,所有相关 when 表达式编译失败,强制更新。
  3. error() 函数抛出 IllegalStateException,表达非法转换。
  4. eventLog 记录事件历史,可用于事件溯源(Event Sourcing)。

11. 参考文献(References)

本章节列出本文档撰写过程中参考的学术论文、官方文档、经典教材与在线资源,便于读者进一步深入研读。

11.1 官方文档

11.2 学术论文

  • Pierce, Benjamin C. Types and Programming Languages. MIT Press, 2002. 第 11 章”Sum Types”与第 15 章”Subtyping”系统讲解代数数据类型与子类型关系的理论基础。
  • Appel, Andrew W. Modern Compiler Implementation in ML. Cambridge University Press, 2004. 第 4 章”Abstract Syntax”与第 5 章”Pattern Matching”涵盖模式匹配的编译实现。
  • Marlow, Simon, and Simon Peyton Jones. “Making a Fast Curry: Push/Enter vs. Eval/Apply for Higher-Order Languages.” Journal of Functional Programming 14.4 (2004): 409-415. 讨论模式匹配的优化编译。
  • Wadler, Philip. “Theorems for Free!” FPCA ‘89: Functional Programming Languages and Computer Architecture, 1989, pp. 347-359. 参数性与型变的理论基础。
  • OCaml Team. “Algebraic Data Types and Pattern Matching.” OCaml Manual, 2023. https://v2.ocaml.org/manual/idx.html
  • Hoare, C. A. R. “Null References: The Billion Dollar Mistake.” QCon, 2009. 空引用问题与类型安全设计的原始讨论。
  • Banken, Bodin, et al. “Space Invaders: Constructing and Reasoning about Inheritance Hierarchies.” POPL ‘18, 2018. 受限继承的形式化讨论。

11.3 经典教材

  • Odersky, Martin, Lex Spoon, and Bill Venners. Programming in Scala, 5th Edition. Artima Press, 2021. 第 7 章”Built-in Control Structures”与第 15 章”Case Classes and Pattern Matching”系统讲解 Scala sealed trait 与模式匹配,是 Kotlin 密封类设计的重要参照。
  • Toroczkai, Zoltan, et al. Algebraic Data Types in Modern Programming Languages. Springer, 2020. ADT 在现代编程语言中的设计与实现综述。
  • Armstrong, Joe. Programming Erlang: Software for a Concurrent World. Pragmatic Bookshelf, 2013. 第 7 章”Errors and Exceptions”讨论 tagged tuple 与密封类型的对比。
  • Bjarnason, Runar, and Paul Chiusano. Functional Programming in Scala. Manning Publications, 2014. 第 3 章”Strictness and Laziness”与第 6 章”Purely Functional State”涵盖 ADT 与状态机的函数式建模。
  • Sestoft, Peter. Programming Language Concepts. Springer, 2017. 第 6 章”Type Systems”涵盖静态类型、型变与穷举检查的理论基础。
  • Kleppmann, Martin. Designing Data-Intensive Applications. O’Reilly Media, 2017. 第 11 章”Stream Processing”讨论事件溯源(Event Sourcing)与状态机建模。

11.4 在线资源与博客

11.5 规范与标准

11.6 引用格式说明

本文档的引用格式遵循 IEEE 引用规范:

  • 学术论文:作者 + 标题 + 期刊/会议 + 年份 + 页码。
  • 书籍:作者 + 书名 + 出版社 + 年份。
  • 在线资源:作者/组织 + 标题 + URL + 访问日期。
  • 规范:编号 + 标题 + 发布组织 + 年份。

12. 延伸阅读(Further Reading)

本章节列出与密封类型相关的高级主题、Kotlin 演进方向、跨语言对比与工程实践资料,供读者在掌握本文档内容后进一步拓展视野。

12.1 高级主题:递归 ADT 与 Catamorphism

12.1.1 Catamorphism(折叠)理论

Catamorphism 是函数式编程中表示”折叠”操作的通用概念,源自范畴论。对于递归 ADT,catamorphism 提供了一种通用的遍历与归纳方法:

  • Pierce, Benjamin C. Basic Category Theory for Computer Scientists. MIT Press, 1991. 第 3 章介绍初始代数(Initial Algebra)与 catamorphism 的数学基础。
  • Meijer, Erik, Maarten Fokkinga, and Ross Paterson. “Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.” FPCA ‘91, 1991. 经典论文,提出 foldrfoldlunfold 等递归模式的组合子。
  • Gibbons, Jeremy. “Patterns in Functional Programming.” University of Oxford, 2019. 现代综述,将 catamorphism 与面向对象设计模式对比。

在 Kotlin 中,递归密封类(如表达式树、JSON 树)天然适合用 catamorphism 建模。读者可尝试实现一个通用 cata 函数,对任意递归密封类进行折叠。

12.1.2 递归数据结构的高级模式

  • Anamorphism:展开(unfold),从种子生成递归数据结构。适合建模无限流、回溯算法。
  • Hylomorphism:cata + ana 的组合,先展开再折叠。适合建模分治算法。
  • Paramorphism:带上下文的折叠,折叠时能访问子结构原值。适合建模有”上下文”的遍历。
  • Apomorphism:带提前终止的展开。适合建模带剪枝的搜索。

12.2 Kotlin 演进方向

12.2.1 模式匹配增强(Pattern Matching)

Kotlin 团队正在讨论引入更强大的模式匹配,借鉴 Scala 与 Rust:

  • 绑定模式(Binding Patterns):如 Some(Some(x)) 形式的嵌套解构,当前 Kotlin 需要嵌套 when 实现。
  • 解构在 when 分支:允许 when (val (a, b) = pair) { ... } 在分支中直接解构。
  • Or 模式:如 is A | is B -> ...,合并多个分支。
  • 守卫(Guards):如 is Circle && radius > 10,分支后跟条件表达式。

参考:

12.2.2 K2 编译器的进一步优化

K2 编译器在 Kotlin 2.0 稳定后,仍有改进空间:

  • 更精确的穷举检查:识别 null 与非空分支的组合、嵌套 when 的穷举传播。
  • 跨模块穷举检查:在 KMP 项目中,编译器能跨模块检查密封类的子类完整性。
  • 更友好的错误信息:精确指出遗漏的子类,并给出快速修复建议。
  • 增量编译优化:修改密封类子类时,仅重新编译受影响的 when 表达式。

12.2.3 与 Java 17+ 密封类的互操作

Kotlin 与 Java 17+ 的密封类互操作仍在改进:

  • Java sealed + permits 的识别:Kotlin 编译器识别 Java 17 的 sealed 类,并将其视为 Kotlin 密封类进行穷举检查。
  • @Metadatapermits 的互转:KMP 项目中,Kotlin 密封类在 JVM 字节码层面可生成 Java 17 兼容的 sealed 修饰符。
  • 跨语言密封类:Java 代码继承 Kotlin 密封类(受限),Kotlin 代码继承 Java 17 sealed 类。

参考:

12.3 跨语言对比

12.3.1 Rust enummatch

Rust 的 enum 是真正的代数数据类型,支持绑定模式与守卫:

enum Expr {
    Const(f64),
    Sum(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
}

fn eval(e: &Expr) -> f64 {
    match e {
        Expr::Const(v) => *v,
        Expr::Sum(l, r) => eval(l) + eval(r),
        Expr::Mul(l, r) => eval(l) * eval(r),
    }
}

Rust 的优势:

  • 绑定模式:Expr::Sum(l, r) 直接解构内部值。
  • 守卫:Expr::Const(v) if *v > 0.0 => ...
  • 零成本抽象:match 编译为跳转表,性能优于 if-else 链。

Kotlin 与 Rust 对比:

  • Rust 表达力更强(绑定模式 + 守卫)。
  • Kotlin 更简单(is + 智能转换),但嵌套解构不优雅。
  • 两者均支持穷举检查,但 Rust 的检查更严格(包括 refutable patterns)。

参考:

12.3.2 Scala sealed traitcase class

Scala 是 Kotlin 密封类设计的主要参照:

sealed trait Expr
case class Const(value: Double) extends Expr
case class Sum(left: Expr, right: Expr) extends Expr

def eval(e: Expr): Double = e match {
  case Const(v) => v
  case Sum(l, r) => eval(l) + eval(r)
}

Scala 的优势:

  • 绑定模式:Const(v) 直接解构。
  • 高阶类型:支持 FunctorMonad 等高阶抽象。
  • 隐式参数:更强大的依赖类型系统。

Scala 的劣势:

  • 编译速度慢:Scala 编译器比 Kotlin 慢 2-3 倍。
  • 学习曲线陡峭:隐式、高级类型让团队协作困难。

参考:

12.3.3 Swift enumswitch

Swift 的 enum 与 Kotlin 密封类高度相似:

enum Expr {
    case Const(Double)
    case Sum(Expr, Expr)
    indirect case Mul(Expr, Expr)
}

func eval(_ e: Expr) -> Double {
    switch e {
    case .Const(let v): return v
    case .Sum(let l, let r): return eval(l) + eval(r)
    case .Mul(let l, let r): return eval(l) * eval(r)
    }
}

Swift 的特点:

  • indirect 关键字:标记递归枚举,告诉编译器使用引用语义。
  • 绑定模式:.Const(let v) 直接解构。
  • 守卫:case .Const(let v) where v > 0:
  • 关联值(Associated Values):枚举值可携带异构数据。

参考:

12.3.4 Haskell datacase

Haskell 是 ADT 与模式匹配的”原产地”:

data Expr = Const Double | Sum Expr Expr | Mul Expr Expr

eval :: Expr -> Double
eval (Const v) = v
eval (Sum l r) = eval l + eval r
eval (Mul l r) = eval l * eval r

Haskell 的特点:

  • 纯函数式:无副作用,模式匹配即函数定义。
  • 惰性求值:递归数据结构可无限延伸(如 Data.Listinfinite list)。
  • 类型类(Type Class):类似接口,但更强大(支持高阶类型)。

参考:

12.4 工程实践与生态

12.4.1 Arrow-kt:函数式编程库

Arrow-kt 是 Kotlin 的函数式编程库,提供了大量基于密封类的高级类型:

  • Either<L, R>:表示”成功或失败”的双向密封类,类似 Rust Result
  • Option<T>:替代 T? 的密封类,提供 mapflatMapfilter 操作。
  • Validated<E, A>:累积错误的验证类型,适合表单校验。
  • NonEmptyList<T>:非空列表密封类,编译期保证非空。
  • Raise<E>:基于协程的”计算上下文”,结合密封类实现错误处理。

参考:

12.4.2 Ktor 与密封类

Ktor 是 JetBrains 的服务器框架,大量使用密封类:

  • HttpStatusCode:密封类表示 HTTP 状态码。
  • PipelinePhase:密封类表示管道阶段。
  • OutgoingContent:密封类表示响应内容类型。
  • PipelineContext:密封类表示管道上下文。

参考:

12.4.3 Jetpack Compose 与密封类

Jetpack Compose 使用密封类建模 UI 状态:

  • Modifier:密封接口,组合各种修饰符。
  • RippleTheme:密封接口,定义涟漪效果主题。
  • LazyListState:密封类,表示懒加载列表状态。
  • ViewModel UI State:推荐使用密封类建模 UI 状态。

参考:

12.4.4 Gradle 与密封类

Gradle 的 Kotlin DSL 使用密封类建模构建配置:

  • Dependency:密封类表示依赖项。
  • Configuration:密封类表示配置。
  • Task:密封类表示任务类型。

参考:

12.5 社区与生态

12.5.1 Kotlin 演进提案(KEEP)

Kotlin 演进提案(Kotlin Evolution and Enhancement Process, KEEP)是社区讨论语言特性的平台:

12.5.2 学术研究

近年来关于 Kotlin 类型系统的学术研究:

  • Krasheninnikov, Dmitry, et al. “Type Inference in Kotlin 2.0 K2 Compiler.” SCAM ‘23: IEEE International Conference on Source Code Analysis and Manipulation, 2023.
  • Belyaev, Mikhail, et al. “JetBrains Research on Kotlin Coroutines.” ACM SIGPLAN Notices 54.10 (2019): 17-28.
  • Ushakov, Daniil. “Kotlin Multiplatform: A Study on Cross-Platform Code Sharing.” IEEE Software 38.6 (2021): 42-49.

12.5.3 开源项目

12.6 学习路径建议

12.6.1 初学者路径(0-3 个月)

  1. 掌握 Kotlin 基础语法(变量、函数、类、控制流)。
  2. 理解 data classenum class 的差异。
  3. 学习 sealed class 的基本语法与 when 表达式。
  4. 实践网络请求结果建模(Result 密封类)。

12.6.2 中级路径(3-12 个月)

  1. 深入理解穷举检查与智能转换的原理。
  2. 学习密封接口与多继承的价值。
  3. 实践 MVI 架构与单向数据流。
  4. 阅读开源项目(kotlinx.coroutines、kotlinx.serialization)的密封类设计。

12.6.3 高级路径(12+ 个月)

  1. 研究递归 ADT 与 catamorphism 理论。
  2. 对比 Rust、Scala、Swift 的模式匹配。
  3. 阅读 K2 编译器源码,理解穷举检查的实现。
  4. 参与社区讨论,为 Kotlin KEEP 提交反馈。

12.7 结语

密封类与密封接口是 Kotlin 类型系统的核心特性之一,它们将”受限继承 + 穷举检查 + 智能转换”三者结合,为构建类型安全的领域模型、状态机与递归数据结构提供了坚实的基础。本文档从形式化定义、理论推导、代码示例、对比分析、陷阱防范、工程实践到案例研究,系统性地覆盖了密封类型的所有重要主题。

读者在完成本文档的学习后,应能够:

  1. 在自己的项目中合理使用密封类与密封接口。
  2. 理解 Kotlin 1.0-2.0 密封类型的演进与设计权衡。
  3. 识别并避免常见陷阱(如滥用 else、跨包子类、智能转换失效)。
  4. 在 MVI、事件溯源、状态机等架构中应用密封类型。
  5. 对比 Kotlin 与其他语言(Scala、Rust、Swift、Java 17)的密封类型设计。

希望本文档能为读者的 Kotlin 学习之旅提供一份系统而深入的参考。如需进一步探讨密封类型相关的高级主题,欢迎在 Kotlin Slack、GitHub Discussions 与社区交流。


文档结束

本文档由 FANDEX 项目团队整理,对标 MIT 6.005、Stanford CS193P、CMU 15-214 教学水准。如发现错误或建议改进,请提交 Issue 或 Pull Request。

返回入门指南