【Kotlin】数据类 data class

在这里插入图片描述
在 Kotlin 中,data class 是一种专门用于存储数据(state)的类。自动为你生成一系列常用方法,比如:

  • toString(),字符串格式类似于"User(name=John, age=42)"
  • equals() / hashCode(),用属性值比较两个对象是否相等
  • copy()
  • componentN(),编译器自动生成形如:fun component1() = name(属性);fun component2() = age;…

基本语法

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

使用示例:

val user1 = User("Tom", 20)
val user2 = user1.copy(age = 25)
val user3 = User("Tom", 20)

println(user1)           // 输出:User(name=Tom, age=20)
println(user2)           // 输出:User(name=Tom, age=25)
println(user1 == user2)  // 输出:false
println(user1 == user3)  // 输出:true

val (n, a) = user1       // 解构
println("$n - $a")       // 输出:Tom - 20

语法规则

  • 主构造函数中至少要有一个参数
data class Empty() // ❌ 错误:没有属性
  • 主构造函数的参数必须是 valvar
  • 数据类不能是 abstractopensealedinner
  • 可以实现接口,可以继承其他类
data class Response(val code: Int, val msg: String): Serializable
  • 如果数据类中自动生成的函数存在显式实现(注意不允许为componentN()copy()提供显式实现),或者父类中有final实现(子类无法覆盖),则不会自动生成这些函数,而使用现有的实现。
data class Person(val name: String, val age: Int) {
    // 显式实现 toString()
    override fun toString(): String {
        return "Hello Person(name=$name, age=$age)"
    }
}

fun main() {
    val p1 = Person("Alice", 30)
    println(p1.toString()) // Hello Person(name=Alice, age=30)
}
open class Base {
    final override fun toString(): String {
        return "Base class"
    }
}

data class Derived(val id: Int) : Base()

fun main() {
    val d = Derived(1)
    println(d.toString())  // Base class
}
  • 如果父类中已经有了某个 componentN() 函数,是open 的,并且返回类型与数据类中该属性类型兼容,那么编译器会自动生成覆盖版本。否则,编译器无法生成覆盖版本,编译错误。
open class Base {
    // 如果是 final 则编译错误
    open fun component1(): String = "A"
    open fun component2(): String = "B"
    // 注意,即使写成 open fun component2(): Double = 1.2 也不会报错!!!
}

data class Derived(val name: String, val age: String) : Base()
// componentN 被重写,返回 name,age

fun main() {
    val d = Derived("Tom", "20")

    val (name, age) = d
    println("$name, $age")   // Tom, 20
}
  • 编译器只会根据【主构造函数】中定义的属性生成对应函数
data class Person(val name: String) {
    var age: Int = 0
}

name —— 定义在主构造函数中 ✅
→ 编译器会为它生成:

  • component1()
  • copy(name=...)
  • equals()hashCode()toString() 中参与比较与输出

age —— 定义在类体中(不是主构造函数)❌
→ 编译器不会为它生成:

  • componentN()
  • copy() 参数
  • equals() / hashCode() / toString() 的比较部分
fun main() {
    val p1 = Person("Alice")
    p1.age = 18

    val p2 = Person("Alice")
    p2.age = 25

    println(p1 == p2) // ✅ true —— 因为只比较 name
    println(p1.toString()) // ✅ Person(name=Alice)
    println(p1.copy()) // ✅ Person(name=Alice)

    // 解构
    val (n) = p1
    println(n) // ✅ Alice
}

关于copy

可以方便地复制一个对象并选择性修改某些属性,在保持不可变风格的同时,快速创建新对象。

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

// 编译器会自动生成类似下面的函数
fun copy(name: String = this.name, age: Int = this.age): User {
    return User(name, age)
}
fun main() {
    val u1 = User("Alice", 20)

    val u2 = u1.copy()
    println(u2) // User(name=Alice, age=20)
    
    println(u1 == u2)  // true
    println(u1.equals(u2))  // 与上面等价
    println(u1 === u2) // false

    // 修改一个字段
    val u3 = u1.copy(age = 25)
    println(u3) // User(name=Alice, age=25)
}

编译器生成的 copy() 方法只包含 主构造函数中的属性

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

val p1 = Person("Bob")
p1.age = 30

val p2 = p1.copy()
println(p2.age) // ⚠️ 输出 18,而不是 30!

不可变对象更新

data class State(val count: Int, val enabled: Boolean)

fun main() {
    val s1 = State(5, true)
    val s2 = s1.copy(count = s1.count + 1)

    println(s1) // State(count=5, enabled=true)
    println(s2) // State(count=6, enabled=true)
}

集合更新

data class User(val name: String, val age: Int)
val users = listOf(User("Tom", 18), User("Jerry", 20))
val updated = users.map {
    if (it.name == "Tom") it.copy(age = 19) else it
}
println(updated) // [User(name=Tom, age=19), User(name=Jerry, age=20)]

copy()equals() 的关系

  • copy() 创建的新对象会重新计算 hashCode()
  • copy() 生成的新对象与原对象比较时,如果属性值一致,则 equals() 返回 true
val u1 = User("Alice", 22)
val u2 = u1.copy()
println(u1 == u2) // ✅ true
println(u1 === u2) // ❌ false(引用不同)

copy()是浅拷贝,对于引用类型属性只会复制它们的引用地址

data class Book(val title: String, val tags: MutableList<String>)

val b1 = Book("Kotlin", mutableListOf("programming"))
val b2 = b1.copy()

b2.tags.add("advanced")
println(b2.tags) // ["programming", "advanced"]
println(b1.tags) // ⚠️ ["programming", "advanced"]

// tags是一个引用类型,copy只是复制了引用。如果你需要完全独立的副本,需要自己实现深拷贝。
data class Book(val title: String, val tags: MutableList<String>) {
    fun deepCopy() = Book(title, tags.toMutableList())
}
### 用法和特性 Kotlin 中的 `data class` 是一种专门用于保存数据的类,它自动提供了许多标准函数的实现,比如 `equals()`, `hashCode()`, `toString()`, 以及 `copy()` 方法。此外,`data class` 还会生成 `componentN()` 函数,这些函数允许使用解构声明来简化对象属性的访问。 #### 定义方法 定义一个 `data class` 非常简单,只需要在类名前加上 `data` 关键字,并在主构造函数中声明属性即可。例如: ```kotlin data class User(val name: String, val age: Int) ``` 在这个例子中,`User` 类有两个属性:`name` 和 `age`,它们都是不可变的(使用 `val` 声明)。Kotlin 会自动为这个类生成以下成员函数: - `equals()` 和 `hashCode()`:用于比较两个对象是否相等,并提供哈希码。 - `toString()`:返回一个字符串表示该对象。 - `copy()`:创建一个新的对象实例,其属性与原对象相同,但可以修改部分属性值。 - `componentN()`:生成一系列的方法,如 `component1()`, `component2()` 等,使得可以通过解构的方式获取对象的各个属性。 #### 示例代码 下面是一些展示 `data class` 特性的示例代码: ```kotlin // 创建一个 User 实例 val user1 = User("Alice", 30) // 使用 toString() 方法 println(user1.toString()) // 输出: User(name=Alice, age=30) // 使用 copy() 方法复制对象并修改某些属性 val user2 = user1.copy(age = 31) // 比较两个对象是否相等 println(user1 == user2) // 输出: false // 解构声明 val (name, age) = user1 println("Name: $name, Age: $age") // 输出: Name: Alice, Age: 30 ``` #### 注意事项 当声明一个 `data class` 时,需要注意以下几点: - 主构造函数至少需要有一个参数。 - 所有主构造函数的参数必须标记为 `val` 或 `var`。 - 数据类不能是抽象的、开放的(open)、密封的(sealed)或者内部的(inner)。 - 数据类不能有私有的属性。 通过使用 `data class`,开发者可以减少大量的样板代码,同时保持代码的清晰和简洁。[^1] ### 相关问题 1. Kotlin 中的 data class 如何实现深拷贝? 2. 在 Kotlindata class 中如何自定义 equals 和 hashCode 方法? 3. 如何在 Kotlindata class 中添加额外的属性而不影响已有的数据结构? 4. Kotlindata class 是否支持继承?如果支持,有哪些限制? 5. 如何在 Kotlin 中使用 data class 进行 JSON 序列化和反序列化?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shayudiandian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值