数据类、密封类与枚举¶
Kotlin 提供了多种专门表达数据和有限状态的类型:data class、sealed class、sealed interface、enum class。它们能减少样板代码,也能让状态建模更安全。
数据类¶
data class User(val id: Long, val name: String)
编译器会基于主构造函数中的属性生成:
equals()hashCode()toString()copy()componentN()解构函数
示例:
val user = User(1, "Ada")
val renamed = user.copy(name = "Grace")
println(user) // User(id=1, name=Ada)
println(renamed) // User(id=1, name=Grace)
Java 对比:Java 16+ 的 record 也能减少数据载体样板代码,但 Kotlin 数据类支持 copy()、默认参数、解构等 Kotlin 风格能力。二者互操作时要分别理解各自生成的 API。
数据类的边界¶
数据类适合表达值对象或 DTO,不适合所有类:
- 不要把复杂业务行为都塞进数据类。
copy()是浅拷贝,不会递归复制内部对象。- 主构造函数以外声明的属性不参与生成的
equals()、hashCode()等方法。
示例:
data class Team(val name: String, val members: MutableList<String>)
val a = Team("Core", mutableListOf("Ada"))
val b = a.copy()
b.members += "Grace"
println(a.members) // [Ada, Grace]
copy() 后两个对象仍共享同一个可变列表引用。
枚举类¶
枚举适合表示一组固定常量:
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
枚举可以有属性和方法:
enum class HttpStatus(val code: Int) {
OK(200),
NOT_FOUND(404),
INTERNAL_ERROR(500);
fun isError(): Boolean = code >= 400
}
Java 枚举也支持属性和方法,Kotlin 枚举的差异主要体现在更自然地配合 when 表达式和 Kotlin 属性语法。
密封类与密封接口¶
密封类型用于表达“有限但每种情况可以携带不同数据”的层级:
sealed interface UiState
data object Loading : UiState
data class Success(val data: String) : UiState
data class Failure(val message: String) : UiState
处理状态:
fun render(state: UiState): String =
when (state) {
Loading -> "加载中"
is Success -> "数据:${state.data}"
is Failure -> "错误:${state.message}"
}
这里不需要 else,因为编译器知道 UiState 的所有直接实现。
密封类 vs 枚举¶
| 需求 | 推荐 |
|---|---|
| 每个值只是固定常量 | enum class |
| 每个分支需要携带不同数据 | sealed class / sealed interface |
| 需要单例状态 | data object 或普通 object |
| 需要每个常量有相同结构属性 | enum class |
| 需要层级扩展但受控 | 密封类型 |
示例:支付方式用密封类通常比枚举更合适:
sealed interface PaymentMethod
data class CreditCard(val number: String, val cvc: String) : PaymentMethod
data class Paypal(val email: String) : PaymentMethod
data object Cash : PaymentMethod
不同支付方式需要不同字段,枚举很难自然表达。
Java sealed 对比¶
Java 15 引入 sealed class,需要配合 permits 声明允许的子类。Kotlin 的密封类更早出现,语法更贴合 when 穷尽检查。
Java 思路:
public sealed interface Result permits Success, Failure {}
Kotlin 思路:
sealed interface Result
data class Success(val value: String) : Result
data class Failure(val reason: String) : Result
Kotlin 的直接子类通常需要在同一包和同一模块内,具体限制还会受到多平台 source set 的影响。
实践建议¶
- DTO 和值对象优先考虑
data class。 - 表示有限状态时,优先考虑密封类型,而不是字符串常量。
- 枚举适合“固定命名值”,不适合每个分支结构差异很大的场景。
copy()是浅拷贝,包含可变集合时要特别小心。when处理密封类型时尽量不写无意义else,保留编译器穷尽检查。
参考¶
- 官方数据类文档:https://kotlinlang.org/docs/data-classes.html
- 官方密封类文档:https://kotlinlang.org/docs/sealed-classes.html
- 官方枚举文档:https://kotlinlang.org/docs/enum-classes.html