Kotlin 2.1.0 入门教程(二十一)数据类
数据类
数据类主要用于存储数据。
对于每个数据类,编译器会自动生成一些额外的成员函数,这些函数支持将实例打印为易读的输出、比较实例、复制实例等操作。
数据类使用 data
关键字标记:
data class User(val name: String, val age: Int)
编译器会根据主构造函数中声明的所有属性,自动派生以下成员:
-
equals() / hashCode()
对。 -
格式为
User(name=John, age=42)
的toString()
函数。 -
与属性声明顺序相对应的
componentN()
函数。 -
copy()
函数(详见下文)。
为确保生成代码的一致性和有意义的行为,数据类必须满足以下要求:
-
主构造函数必须至少有一个参数。
-
主构造函数的所有参数都必须标记为
val
或var
。 -
数据类不能是
abstract
(抽象的)、open
(开放的)、sealed
(密封的)或inner
(内部的)。
此外,关于数据类成员的继承,其生成遵循以下规则:
-
如果数据类主体中有
equals()
、hashCode()
或toString()
的显式实现,或者超类中有这些函数的final
实现,那么不会生成这些函数,而是使用现有的实现。 -
如果超类型有
open
(开放的)且返回兼容类型的componentN()
函数,那么会为数据类生成相应的函数,并覆盖超类型的这些函数。如果由于签名不兼容或超类型的函数是final
而无法覆盖,将会报错。 -
不允许为
componentN()
和copy()
函数提供显式实现。
数据类可以继承其他类。
open class Person(val name: String) {
open fun introduce() {
println("My name is $name.")
}
}
// 定义一个继承自 Person 的数据类。
data class Employee(val id: Int, val name2: String, val department: String) : Person(name2) {
override fun introduce() {
super.introduce()
println("I'm an employee with ID $id, working in the $department department.")
}
}
fun main() {
// 创建 Employee 数据类的实例。
val employee = Employee(1, "John", "IT")
// My name is John.
// I'm an employee with ID 1, working in the IT department.
employee.introduce()
println(employee) // Employee(id=1, name2=John, department=IT)
// 创建另一个相同属性的 Employee 实例。
val anotherEmployee = Employee(1, "John", "IT")
// 比较两个实例是否相等。
println("Are they equal? ${employee == anotherEmployee}") // Are they equal? true
}
在 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
属性,并且只有一个组件函数 component1()
。age
属性是在类体中声明的,因此被排除在外。所以,两个 name
相同但 age
值不同的 Person
对象会被视为相等,因为 equals()
方法仅评估主构造函数中的属性:
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)
复制对象
可以使用 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)
数据类与解构声明
为数据类生成的组件函数 componentN()
使得数据类可以用于解构声明:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // Jane, 35 years of age
标准数据类
标准库提供了 Pair
和 Triple
类。不过,在大多数情况下,使用具名数据类是更好的设计选择,因为具名数据类能为属性提供有意义的名称,从而使代码更易读。