Skip to content

数据类 (Data Classes)

源: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)

编译器生成的成员

编译器会基于主构造函数中声明的属性,自动生成以下成员:

  1. equals() / hashCode():确保内容相同的两个对象相等。
  2. toString():输出格式为 User(name=Alice, age=20)
  3. componentN():支持解构声明。
  4. 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, toStringcopy 的生成。定义在类体内部的属性会被忽略。

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

限制条件

数据类必须满足:

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

总结

  • 极简主义:一行代码替代 Java 百行样板。
  • 不可变优先:利用 copy() 维护数据的一致性。
  • 相等性:自动生成正确的 equals/hashCode,避免手写错误。
  • 解构:方便但需警惕字段顺序变更。