你有没有遇到过这样的场景?
kotlin
data class User(val name: String, val nickname: String)
val (first, second) = user
println("姓名:$first,昵称:$second")
看起来没问题。直到某天你发现输出结果反了------first 其实是 nickname,second 才是 name。
你手滑写反了位置,但编译器一声不吭。
这个 Bug,我犯过好几次。尤其是在两个属性类型一样的时候,问题极难发现。
而 Kotlin 官方也意识到了这个问题。所以,他们决定做一次大改动 ------ 按名字解构。
例如,val (name, age) = person 会从 person 中提取 name 和 age 属性,无论它们是以什么方式、以什么顺序定义的。
从当前以位置为关键元素的做法,转向以名字为中心的做法。
本文将解释这一变化背后的原因、迁移策略,以及 Kotlin 的工具链如何支持这一过程。
速览
从 Kotlin 2.3.20 开始,你需要加上 -Xname-based-destructuring 才能开启按名称解构的功能。
这个参数支持三个值,我们一一讲解。
only-syntax
当使用 -Xname-based-destructuring=only-syntax 时,Kotlin 会新增两种新写法------用圆括号按名字解构,用方括号按位置解构。
注意,这里说的是新增!
原来的代码:val (name, age) = person 依然是按照位置解构的。
你想使用按名字解构,需要这么写:(val name, val age) = person。
同时新增了一个按位置解构的语法,需要这么写:val [name, age] = person,使用方括号按位置解构。
对于为什么使用方括号,这是个彩蛋。
complete
这个参数很简单,现有的代码 val (name, age) = person 直接变成按名字匹配,不再按位置。
现在项目使用这个参数其实很危险,可能会出现大量 Bug,所以还是谨慎使用为好。
name-mismatch
这个参数不会修改任何行为,只会在数据类中基于位置的解构所使用的变量名与属性名不一致时,发出警告。
可以当做一个迁移助手,修改现有代码,如果都改好了,再使用 complete。
为什么要做这个改动
要回到这个问题,我们先看懂什么是解构。
Kotlin 里经常这样写:
kotlin
data class Person(val name: String, val age: Int)
fun checkPerson(p: Person) {
val (name, age) = p // 这就是解构
return name.isNotEmpty() && age >= 0
}
val (name, age) = p 这行代码的意思是把 Person 的两个属性分别取出来,赋值给 name 和 age。这叫做把值解构为它的各个组成部分。
但是这个写法,实际上是有大问题的!
问题一:名字和属性之间没有关联
现在的 Kotlin 是按位置解构的。也就是说,解构时写的名字可以随便取,跟属性名没关系:
kotlin
// 跟上面的代码完全等价!
fun checkPerson(p: Person) {
val (foo, bar) = p // foo 对应 name,bar 对应 age
return foo.isNotEmpty() && bar >= 0
}
这看起来自由,但很容易出错。
比如手滑把两个属性写反了,编译器不一定能发现,这个错误可能要到类型不匹配的时候才会暴露出来,而且出错的地方离真正的原因可能很远。
这个 Bug 我犯过几次,尤其是两个属性的类型是一样的时候。问题很难发现!
问题二:重构时的痛苦
假设你原来这样定义:
kotlin
data class Person(val name: String, val age: Int)
后来想把 age 改成根据生日计算:
kotlin
data class Person(val name: String, val birthdate: Date) {
val age = (Date.now() - birthdate).years
}
因为 age 不再是构造函数中的属性了!
所有解构 age 的地方都会突然变成解构 birthdate,全得改一遍。
问题三:跟抽象不对付
如果把 Person 改成接口,之前的解构就失效了:
kotlin
interface Person {
val name: String
val age: Int
// 必须手动写这些,很麻烦
operator fun component1(): String = name
operator fun component2(): Int = age
}
所以大多数接口都不会提供这种支持。
看名字,不看位置

所以,Kotlin 推出了一套新的解构方案。
如果解构时按名字匹配,上面这些问题就都没了:
- 属性顺序随便改------名字没变,解构代码不用动
- 计算属性、主属性随便换------名字没变,解构代码不用动
- 类、接口、对象随便用------名字对了就行
属性的名字是一个稳定的特征,这意味着源码不需要做任何改动。
新语法长什么样
你可以通过给编译器传参数 -Xname-based-destructuring=only-syntax 来启用新语法。
按名字解构:括号里写 val
老写法:
kotlin
val (name, age) = person
新写法:
kotlin
fun checkPerson(p: Person): Boolean {
(val name, val age) = p
return name.isNotEmpty() && age >= 0
}
区别在哪?
新写法里每个属性前面都有 val,而且顺序不重要:
kotlin
// 下面两种写法效果一样
(val name, val age) = person
(val age, val name) = person // 顺序换了,但结果一样
还可以重命名:
kotlin
// 把 name 属性取出来后叫 theName
fun checkPerson(p: Person): Boolean {
(val age, val theName = name) = p // theName 指向 name
return theName.isNotEmpty() && age >= 0
}
按位置解构:用方括号
有些情况还是得按位置,比如 Pair、Triple,或者集合。
从概念上说,Pair 和 Triple 的组件并没有名字,所以也没必要强迫你在代码里写 first 和 second。按位置解构也可以用于集合,因为集合没有可用的属性名。
新的按位置解构语法使用方括号------跟即将推出的集合字面量语法保持一致(这个彩蛋后续文章会简单说下)。
你可以选择把 val 放在方括号外面或里面。
kotlin
fun isZero(point: Pair<Int, Int>): Boolean {
val [x, y] = point // 一种写法
[val x, val y] = point // 另一种写法
return x == 0 && y == 0
}
在哪都能用
所有这些新语法在任何可以解构的地方都能用,包括 lambda 表达式和循环:
kotlin
// 遍历 Map
for ([key, value] in map) {
println("$key -> $value")
}
// Lambda 里
person?.let { (val name, val years = age) ->
"$name 今年 $years 岁"
}
再强调一遍:这些都是新语法,从 2.3.20 版本开始,编译器已经能识别它们了。
何去何从的圆括号
在未来某个时候,Kotlin 打算让所有使用圆括号的解构都变成按名字的。
当然,如果你想现在就全盘体验,可以通过 -Xname-based-destructuring=complete 编译器参数来提前体验这个未来。
不过,如果你已经有一个项目,直接切换可能会带来很大影响。最明显的问题是某些解构会突然失效,代码需要更新。更危险的是那些仍然能编译通过、但含义已经变了的情况。
因此,编译器内置了一个迁移助手,通过 -Xname-based-destructuring=name-mismatch 参数启用。启用后,编译器会在"按位置和按名字行为不一致"或"一旦圆括号解构不再按位置就无法通过编译"的情况下给出警告。
kotlin
val (name, age) = person // ✅ 两种模式都接受,行为相同
val (age, name) = person // ⚠️ 两种模式都接受,但行为会变
val (personName, personAge) = person // ⚠️ 只有按位置时才接受
// IDE 会自动给出修复建议:
// - 重命名:(val personName = name, val personAge = age) = person
// - 改用方括号:val [personName, personAge] = person
时间表
给出整个按名称解构的时间表:
| 版本 | 时间 | 内容 |
|---|---|---|
| 2.3.20 | 现在 | 实验阶段,需要手动加编译器参数。IntelliJ IDEA 的支持可能还不完善,尤其是迁移方面。 |
| 2.5.0 | 2026 年底 | 稳定版,新语法默认可用,编译器开始报告迁移提示,IntelliJ IDEA 会包含检查工具和快速修复功能。这个阶段大致对应 name-mismatch,不过具体报告方式可能会根据用户反馈调整。 |
| 2.7.0 | 2027 年底 | 圆括号解构默认按名字匹配。你可以通过 complete 参数提前迁移到这个阶段。 |
这是一个很大的改动,Kotlin 不会操之过急。如果在 2027 年的任何时候发现生态还没准备好,可能会推迟到下一个主版本。
数据类不会变
放心 :数据类的 component1()、component2() 这些方法不会消失,字节码跟现在一样。基于名字的解构只是在使用的地方多了一种选择。
但未来的多字段值类(value classes)不会生成这些方法,所以值类的解构将只能使用基于名字的方式。
你还记得 value class 有什么用吗?
总结
| 方面 | 现在 | 未来 |
|---|---|---|
| 按位置解构 | val (name, age) = p |
val [name, age] = p(方括号) |
| 按名字解构 | 需编译器参数 | (val name, val age) = p(圆括号里写 val) |
| 变量顺序 | 必须和属性顺序一致 | 随便写,按名字找 |
| 重命名 | 不支持 | (val n = name, val a = age) = p |
核心变化:以后解构看名字,不看顺序。