AI Summary 173.21585.1 Kotlin 数据类与解构摘要 2026-10-13
Kotlin 数据类与解构 数据类专门用来保存数据,只要在 class 前面加上 data,编译器就会自动生成一组常用方法。 这些方法包括 equals、hashCode、toString、componentN 和 copy,省去大量重复代码。 配合解构声明,还能把对象里的属性一次性拆到多个变量中。 定义一个数据类 数据类的写法很简单,主构造器里至少要有一个参数,并且参数都要标上 val 或 var。 实例 [mycode4 t..

Kotlin 数据类与解构

数据类专门用来保存数据,只要在 class 前面加上 data,编译器就会自动生成一组常用方法。

这些方法包括 equals、hashCode、toString、componentN 和 copy,省去大量重复代码。

配合解构声明,还能把对象里的属性一次性拆到多个变量中。


定义一个数据类

数据类的写法很简单,主构造器里至少要有一个参数,并且参数都要标上 val 或 var。

实例

// data 关键字写在 class 之前
data class sohoSite(val name: String, val url: String, val age: Int)

fun main() {
    val site = sohoSite("启蒙教材", "www.facesoho.com", 12)

    println(site)      // 自动生成的 toString()
    println(site.name) // 属性照常访问

    val same = sohoSite("启蒙教材", "www.facesoho.com", 12)
    println(site == same)                              // 自动生成的 equals()
    println(site.hashCode() == same.hashCode())        // 自动生成的 hashCode()
}
sohoSite(name=启蒙教材, url=www.facesoho.com, age=12)
启蒙教材
true
true

如果用普通 class 写同样的内容,这些方法都得自己实现,非常啰嗦。


自动生成的成员

data class 生成的方法都有固定的语义,下面这张表可以当作速查。

成员签名作用
equals()equals(other: Any?): Boolean按主构造器属性逐个比较
hashCode()hashCode(): Int与 equals 保持一致,保证放进散列表时行为正确
toString()toString(): String输出「类名(属性=值, 属性=值)」
componentN()component1()、component2() 等支持解构声明,顺序与主构造器参数一致
copy()copy(...): 类名复制出一个新对象,可以只改其中几个属性

只有主构造器里的属性会参与 equals、hashCode、toString 和 componentN。

写在类体里的属性不参与这些方法的生成。


生成条件

并不是随便加个 data 就能变成数据类,下面几条必须同时满足。

条件说明反例
主构造器至少一个参数没有参数无法生成有意义的方法data class Empty()
参数必须标 val 或 var否则参数不是属性,无法参与生成data class User(name: String)
不能是 abstract抽象类无法实例化abstract data class A(val id: Int)
不能是 open数据类本身是 finalopen data class B(val id: Int)
不能是 sealed密封类属于另一套体系sealed data class C(val id: Int)
不能是 inner内部类无法作为数据类inner data class D(val id: Int)

实例

// 正确:主构造器至少一个参数,且都是 val / var
data class sohoA(val id: Int)

// 错误:主构造器没有参数
// data class sohoB()   // Data class must have at least one primary constructor parameter

// 错误:参数没有 val / var,只是一个普通形参
// data class sohoC(name: String)

// 错误:不能是 abstract / open / sealed / inner
// abstract data class sohoD(val id: Int)
// open data class sohoE(val id: Int)

fun main() {
    println(sohoA(1))
}
sohoA(id=1)

数据类可以实现接口,但本身是 final 的,不能再被继承。

它也可以带类体,在类体里定义额外的函数和属性。


copy() 与部分修改

数据类适合存放不可变数据,修改某个字段时通常用 copy 生成一个新对象,而不是改原对象。

copy 的每个参数都有默认值,默认取原对象对应的属性,因此可以只写要改的那几个。

实例

data class sohoSite(val name: String, val url: String, val age: Int)

fun main() {
    val site = sohoSite("启蒙教材", "www.facesoho.com", 12)

    // 只改 url,其余字段保持原值
    val mirror = site.copy(url = "m.facesoho.com")
    println(mirror)

    // 一次改多个字段
    val renamed = site.copy(name = "SOHO 教程", age = 13)
    println(renamed)

    // 不传任何参数 = 浅拷贝一个新对象
    val same = site.copy()
    println(same == site)    // 属性相同,结构相等
    println(same === site)   // 但不是同一个对象
}
sohoSite(name=启蒙教材, url=m.facesoho.com, age=12)
sohoSite(name=SOHO 教程, url=www.facesoho.com, age=13)
true
false

copy 是浅拷贝,对象里的引用类型字段仍然指向同一个实例。


解构声明

数据类会自动生成 component1、component2 等函数,因此可以把对象拆成多个变量。

变量的顺序对应主构造器参数的顺序,不是属性在类体里的书写顺序。

实例

data class sohoSite(val name: String, val url: String, val age: Int)

fun main() {
    val site = sohoSite("启蒙教材", "www.facesoho.com", 12)

    // 按主构造器参数顺序解构
    val (name, url, age) = site
    println("名称:$name,网址:$url,年龄:$age")

    // 只关心其中一个字段,其余用下划线占位(Kotlin 1.1 起)
    val (_, siteUrl, _) = site
    println("只要网址:$siteUrl")

    // 遍历 Map 时同样使用解构
    val scores = mapOf("soho" to 95, "SOHO" to 88)
    for ((key, value) in scores) {
        println("$key -> $value")
    }
}
名称:启蒙教材,网址:www.facesoho.com,年龄:12
只要网址:www.facesoho.com
soho -> 95
SOHO -> 88

普通类只要自己声明 componentN 运算符函数,也能支持解构。

实例

class sohoPoint(val x: Int, val y: Int) {
    // 手动声明 componentN,让普通类也支持解构
    operator fun component1() = x
    operator fun component2() = y
}

fun main() {
    val (x, y) = sohoPoint(3, 4)
    println("x=$x, y=$y")
}
x=3, y=4

注意:解构中的下划线占位符从 Kotlin 1.1 开始支持。老版本里如果想跳过某个位置,只能给它取个用不到的名字。


数据类的相等性

数据类的 equals 只比较主构造器属性,类体里的属性不参与。

这意味着两个对象可能相等,但它们的类体属性并不相同。

实例

data class sohoUser(val name: String) {
    var city: String = "北京"   // 类体属性,不参与 equals / hashCode
}

fun main() {
    val a = sohoUser("soho")
    val b = sohoUser("soho")
    a.city = "上海"
    b.city = "广州"

    println(a == b)            // 只比较 name,所以是 true
    println(a.city == b.city)  // 类体属性单独比较,false
}
true
false

这也提醒我们:需要参与相等性判断的字段,都应该放进主构造器。


Pair 与 Triple

标准库提供了两个现成的数据类,用来临时组合两到三个值。

Pair 表示两个值,Triple 表示三个值,它们都支持解构和 copy。

实例

fun main() {
    // to 是中缀函数,等价于 Pair("soho", 95)
    val score: Pair<String, Int> = "soho" to 95
    println(score.first)    // 第一个值
    println(score.second)   // 第二个值

    // 解构 Pair
    val (name, value) = score
    println("$name 的分数是 $value")

    // Triple 表示三个值
    val info = Triple("SOHO", "www.facesoho.com", 2026)
    val (site, url, year) = info
    println("$site - $url - $year")

    // Pair 也是数据类,同样有 copy
    println(score.copy(second = 100))
}
soho
95
soho 的分数是 95
SOHO - www.facesoho.com - 2026
(soho, 100)

Pair 和 Triple 适合函数临时返回多个值,字段含义不明确时建议改用自定义数据类。


data object(Kotlin 1.9 起)

普通 object 打印出来是一串对象地址,data object 会自动生成有意义的 toString。

Kotlin 1.9 起可以用 data object 声明单例,它还会生成 equals 和 hashCode。

实例

// Kotlin 1.9 起:data object 自动生成 toString / equals / hashCode
data object sohoConfig {
    val siteName: String = "SOHO"
}

fun main() {
    println(sohoConfig)                    // 输出对象名,而不是地址
    println(sohoConfig == sohoConfig)    // 单例,永远相等
    println(sohoConfig.siteName)
}
sohoConfig
true
SOHO

data object 最常见的用途是放在密封类层次里表示没有数据的分支。

实例

sealed interface sohoState {
    data object Loading : sohoState                        // 无数据的单例分支
    data object Empty : sohoState                          // 无数据的单例分支
    data class Success(val data: String) : sohoState       // 带数据的分支
}

fun describe(state: sohoState): String = when (state) {
    sohoState.Loading -> "加载中"
    sohoState.Empty -> "暂无数据"
    is sohoState.Success -> "成功:${state.data}"
}

fun main() {
    println(describe(sohoState.Loading))
    println(describe(sohoState.Empty))
    println(describe(sohoState.Success("www.facesoho.com")))
}
加载中
暂无数据
成功:www.facesoho.com

常见问题

下面整理几个使用数据类时容易遇到的问题。

为什么改完属性没有生效

数据类的属性通常声明为 val,本身不可修改,正确做法是用 copy 生成新对象。

如果确实需要可变属性,把它写成 var,但要留意它仍然会参与 equals 和 hashCode。

类体里的属性为什么不参与比较

这是编译器的约定:只有主构造器参数才会生成到 equals、hashCode 和 toString 中。

需要比较的字段就放进主构造器,不要放在类体里。

copy 之后两个对象共享内部对象怎么办

copy 只复制引用,不复制引用指向的对象,所以是浅拷贝。

如果内部有可变集合,需要自己手动深拷贝一份。

Pair 和自定义数据类怎么选

只在函数内部临时返回两三个值,用 Pair 或 Triple 更省事。

一旦这些值会跨越模块传递,或者需要明确的字段名,就换成自定义数据类。