数据类 (Data Classes)
在 Java 中,为了创建一个简单的 POJO(普通 Java 对象),我们需要编写大量的样板代码(getter/setter, equals(), hashCode(), toString())。Kotlin 通过 data class 彻底解决了这个问题,让数据模型回归其本质:承载数据。
基础声明
只需在 class 前添加 data 关键字:
kotlin
data class User(val name: String, val age: Int)编译器生成的成员
编译器会基于主构造函数中声明的属性,自动生成以下成员:
equals()/hashCode():确保内容相同的两个对象相等。toString():输出格式为User(name=Alice, age=20)。componentN():支持解构声明。copy():用于创建一个修改部分属性的新对象。
复制与不可变性
在函数式编程和现代多线程架构中,不可变性 (Immutability) 是核心原则。数据类通过 copy() 函数完美支持了这一模式。
我们通常将属性声明为 val(只读),当需要修改时,不是去改变原对象,而是复制一个新对象。
kotlin
val jack = User(name = "Jack", age = 1)
// 创建一个副本,age 变为 2,name 保持不变
val olderJack = jack.copy(age = 2)
println(jack) // User(name=Jack, age=1) - 原对象未变
println(olderJack) // User(name=Jack, age=2) - 新对象解构声明 (Destructuring Declarations)
数据类生成的 componentN() 函数允许我们将对象直接拆解为多个变量。
kotlin
val (name, age) = jack
println("$name is $age years old")避免解构陷阱
解构是基于位置的,而不是基于名称。如果你在数据类中调整了字段顺序(例如把 age 放在 name 前面),所有解构调用的逻辑都会出错,且编译器不会报错(如果类型兼容)。因此,在公共 API 中需谨慎使用解构。
属性声明的范围
只有定义在主构造函数中的属性才会参与 equals, hashCode, toString 和 copy 的生成。定义在类体内部的属性会被忽略。
kotlin
data class Person(val name: String) {
var age: Int = 0
}
val p1 = Person("Alice").apply { age = 10 }
val p2 = Person("Alice").apply { age = 20 }
// true!因为 age 不在主构造函数中,不参与 equals 比较
println(p1 == p2)与 JSON 序列化的配合
无参构造函数问题
许多 Java 序列化框架(如 Gson)需要一个无参构造函数。
- 方案 A:给所有属性提供默认值。kotlin
data class User(val name: String = "", val age: Int = 0) - 方案 B:使用官方的
kotlinx.serialization或 Moshi,它们能完美处理无默认值的 Kotlin 构造函数。
JavaBean 规范
如果需要生成标准的 getXxx() / setXxx() 方法以兼容某些 Java 框架,可以使用 @get:JvmName 注解或 Gradle 插件(如 kotlin-jpa)。
限制条件
数据类必须满足:
- 主构造函数至少有一个参数。
- 主构造函数参数必须用
val或var标记。 - 不能是
abstract,open,sealed或inner。
总结
- 极简主义:一行代码替代 Java 百行样板。
- 不可变优先:利用
copy()维护数据的一致性。 - 相等性:自动生成正确的
equals/hashCode,避免手写错误。 - 解构:方便但需警惕字段顺序变更。