Skip to content

数据类与密封类

源:Kotlin 数据模型官方指南

Kotlin 提供了两种强大的工具来构建数据模型:Data Class(持有数据)与 Sealed Class(持有状态/层级)。

数据类 (Data Class)

核心哲学:数据重于行为。编译器会自动从主构造函数中声明的所有属性导出以下成员:

  • equals() / hashCode()
  • toString() 格式为 "User(name=John, age=42)"
  • componentN() 函数(用于解构)
  • copy() 函数

定义约束

  1. 主构造函数至少包含一个参数。
  2. 所有主构造参数必须标记为 valvar
  3. 不能是 abstract, open, sealedinner

不可变性与 copy()

在多线程或响应式架构(如 MVI)中,不可变数据是核心。copy() 允许我们在修改部分属性的同时保持对象不可变。

kotlin
data class User(val id: Int, val name: String)

val user1 = User(1, "Bruce")
// 创建复本,只修改名字,id 保持不变
val user2 = user1.copy(name = "Virogu")

解构声明 (Destructuring)

kotlin
val (id, name) = user1
println("ID: $id, Name: $name")

顺序陷阱

解构是基于位置(顺序)的,而不是基于名称。 如果你调整了 data class 构造参数的顺序,解构逻辑可能会默默通过但赋值错误(如果类型兼容)。 建议:仅在局部且范围极小的代码块中使用解构。

密封类 (Sealed Class)

密封类用于表示受限的类层级。它本质上是一个枚举的扩展:枚举的实例是单例,而密封类的子类可以有多个实例且持有不同状态。

核心价值:穷举性 (Exhaustiveness)

当在 when 表达式中使用密封类时,编译器强制要求覆盖所有子类。这在 Android UI State 管理中至关重要。

kotlin
sealed interface UiState {
    data object Loading : UiState // 单例使用 data object (Kotlin 1.9+)
    data class Success(val data: List<String>) : UiState
    data class Error(val msg: String) : UiState
}

fun render(state: UiState) {
    // 编译器保证:如果新增了状态,这里会报错提醒修改
    when (state) {
        UiState.Loading -> showProgressBar()
        is UiState.Success -> showList(state.data)
        is UiState.Error -> showToast(state.msg)
    }
}

密封类 vs 密封接口 (Sealed Interface)

Kotlin 1.5 引入了密封接口。

特性Sealed ClassSealed Interface
状态持有可包含具体属性/构造函数不持有状态 (无构造函数)
继承限制单继承多实现
类优化类似于 Abstract Class纯接口

选型建议

  • 如果所有子类共用某些逻辑或属性 -> 用 Sealed Class
  • 如果仅仅是为了标记类型层级,或者子类已继承了其他父类 -> 用 Sealed Interface

密封类 vs 枚举 (Enum)

kotlin
// 枚举:实例是常量,无法持有动态数据
enum class ConnectionState {
    CONNECTING, CONNECTED, DISCONNECTED
}

// 密封类:实例可携带数据
sealed class Result
data class Success(val data: String) : Result() // 携带数据
object Failure : Result()