5.8.2 数据类

原文链接: https://kotlinlang.org/docs/data-classes.html

5.8.2 数据类

Kotlin 中的数据类主要用于保存数据。对于每个数据类,编译器都会自动生成额外的成员函数,让你能以可读的形式打印实例、比较实例、复制实例等。数据类用 data 标记:

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

编译器会根据主构造函数中声明的所有属性自动派生以下成员:

为确保生成代码的一致性和有意义的行为,数据类必须满足以下要求:

  • 主构造函数必须至少有一个参数。
  • 所有主构造函数参数都必须标记为 val 或 var。
  • 数据类不能是 abstract、open、sealed 或 inner。

此外,数据类成员的生成在成员的继承方面遵循以下规则:

  • 如果数据类主体中已有 equals()、hashCode() 或 toString() 的显式实现,或者超类中有它们的 final 实现,那么这些函数不会被生成,而是使用已有的实现。
  • 如果某个超类型有 open 且返回兼容类型的 componentN() 函数,则会为数据类生成相应的函数并重写超类型的那些函数。如果由于签名不兼容或因为是 final 而无法重写超类型的函数,则会报错。
  • 不允许为 componentN() 和 copy() 函数提供显式实现。

数据类可以继承其他类(示例见密封类)。

注意: 在 JVM 上,如果生成的类需要有无参数构造函数,就必须为属性指定默认值(见构造函数):kotlin data class User(val name: String = "", val age: Int = 0)

在类体中声明的属性

编译器只使用主构造函数中定义的属性来生成自动函数。要把某个属性排除在生成的实现之外,请在类体中声明它:

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

在下面的例子中,默认情况下只有 name 属性会被 toString()、equals()、hashCode() 和 copy() 的实现使用,并且只有一个组件函数 component1()。age 属性声明在类体中,因此被排除在外。所以,两个 name 相同但 age 不同的 Person 对象会被视为相等,因为 equals() 只计算主构造函数中的属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
data class Person(val name: String) {
    var age: Int = 0
}
fun main() {
    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}")
    // age 为 10 的 person1: Person(name=John)

    println("person2 with age ${person2.age}: ${person2}")
    // age 为 20 的 person2: Person(name=John)
}

复制

使用 copy() 函数可以复制对象,让你在保持其余属性不变的同时修改其部分属性。上面 User 类的该函数实现如下:

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

然后你就可以这样写:

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

copy() 函数创建的是实例的浅复制。换句话说,它不会递归复制各个组成部分。因此,对其他对象的引用是共享的。

例如,如果某个属性持有可变列表,那么通过"原始"值所做的修改在副本中也能看到,通过副本所做的修改在原始值中也能看到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
data class Employee(val name: String, val roles: MutableList<String>)

fun main() {
    val original = Employee("Jamie", mutableListOf("developer"))
    val duplicate = original.copy()

    duplicate.roles.add("team lead")

    println(original)
    // Employee(name=Jamie, roles=[developer, team lead])
    println(duplicate)
    // Employee(name=Jamie, roles=[developer, team lead])
}

可以看到,修改 duplicate.roles 属性同时也会改变 original.roles 属性,因为这两个属性共享同一个列表引用。

数据类与解构声明

为数据类生成的组件函数使得它们可以用于解构声明:

1
2
3
4
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age

标准数据类

标准库提供了 Pair 和 Triple 类。不过在大多数情况下,具名数据类是更好的设计选择,因为它们为属性提供了有意义的名称,让代码更易读。