Edit Page

数据类(Data Class)

最终更新: 2024/03/21

Kotlin 中数据类(Data Class)的主要用来保存数据. 对每个数据类, 编译器会自动生成一些额外的成员函数, 可以用来将对象输出为可读的格式, 比较对象实例, 复制对象实例, 等等. 数据类通过 data关键字标记:

data class User(val name: String, val age: Int)

编译器会根据主构造器中声明的全部属性, 自动推断产生以下成员函数:

  • .equals()/.hashCode() 函数对.
  • .toString() 函数, 输出格式为 "User(name=John, age=42)".
  • .componentN() 函数群, 这些函数与类的属性对应, 函数名中的数字 1 到 N, 与属性的声明顺序一致.
  • .copy() 函数 (详情见下文).

为了保证自动生成的代码的行为一致, 并且有意义, 数据类必须满足以下所有要求:

  • 主构造器必须有一个以上参数.
  • 主构造器的所有参数必须标记为 valvar.
  • 数据类不能是抽象类, open 类, 封闭(sealed)类, 或内部(inner)类.

此外, 考虑到成员函数继承的问题, 成员函数的生成遵循以下规则:

  • 对于 .equals(), .hashCode().toString() 函数, 如果在数据类的定义体中存在明确的实现, 或在超类中存在 final 的实现, 那么这些成员函数不会自动生成, 而会使用已存在的实现.
  • 如果超类存在 open.componentN() 函数, 并且返回一个兼容的数据类型, 那么子类中对应的函数会自动生成, 并覆盖超类中的函数. 如果超类中的函数签名不一致, 或者是 final 的, 导致子类无法覆盖, 则会报告编译错误.
  • 不允许对 .componentN().copy() 函数提供明确的实现(译注, 这些函数必须由编译器自动生成).

数据类可以继承其他类 (示例请参见 封闭类(Sealed class)).

在 JVM 平台, 如果自动生成的类需要拥有一个无参数的构造器, 那么需要为属性指定默认值 (参见 构造器):

data class User(val name: String = "", val age: Int = 0)

在类主体部声明的属性

编译器对自动生成的函数, 只使用主构造器中定义的属性. 如果想要在自动生成的函数实现中排除某个属性, 你可以将它声明在类的主体部:

data class Person(val name: String) {
    var age: Int = 0
}

在下面的示例中, 在 .toString(), .equals(), .hashCode(), 和 .copy() 函数的实现中, 默认只使用了 name 属性, 而且只存在 1 个组件函数 .component1(). age 属性定义在类的 body 部, 因此被排除了. 所以, 两个 Person 对象拥有相同的 name, 不同的 age 值, 它们会被认为值相等. 因为 .equals() 只计算主构造器中的属性:

data class Person(val name: String) {
    var age: Int = 0
}
fun main() {
//sampleStart
  val person1 = Person("John")
  val person2 = Person("John")
  person1.age = 10
  person2.age = 20

  println("person1 == person2: ${person1 == person2}")
  // 输出结果为 person1 == person2: true

  println("person1 with age ${person1.age}: ${person1}")
  // 输出结果为 person1 with age 10: Person(name=John)

  println("person2 with age ${person2.age}: ${person2}")
  // 输出结果为 person2 with age 20: Person(name=John)
//sampleEnd
}

对象复制

使用 .copy() 函数来复制对象, 可以修改 一部分 属性值, 但保持其他属性不变. 对于前面示例中的 User 类, 函数的实现将会是下面这样:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

因此你可以编写下面这样的代码:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

数据类中成员数据的解构

编译器会为数据类生成 组件函数(Component function), 有了这些组件函数, 就可以在 解构声明(destructuring declaration) 中使用数据类:

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") 
// 输出结果为 Jane, 35 years of age

标准库中的数据类

Kotlin 的标准库提供了 PairTriple 类可供使用. 但大多数情况下, 使用有具体名称的数据类是一种更好的设计方式, 因为, 数据类可以为属性指定有含义的名称, 因此可以让代码更加易读.