析构声明
有时将一个对象析构为多个变量会很方便,例如:
val (name, age) = person这种语法被称为析构声明。析构声明可以一次性创建多个变量。 你已经声明了两个新变量:name 和 age,并且可以独立使用它们:
println(name)
println(age)析构声明会被编译为以下代码:
val name = person.component1()
val age = person.component2()component1() 和 component2() 函数是 Kotlin 中广泛使用的约定原则(principle of conventions)的另一个例子(请参阅 + 和 * 等运算符以及 for 循环作为示例)。 只要能在析构声明右侧的对象上调用所需数量的组件函数,该对象就可以位于右侧。当然,也可以有 component3() 和 component4() 等等。
componentN()函数需要使用operator关键字标记,才允许在析构声明中使用。
析构声明也适用于 for 循环:
for ((a, b) in collection) { ... }变量 a 和 b 会获取在集合元素上调用 component1() 和 component2() 返回的值。
示例:从函数返回两个值
假设你需要从一个函数返回两个内容——例如,一个结果对象和某种状态。 在 Kotlin 中实现这一点的紧凑方法是声明一个数据类并返回其实例:
data class Result(val result: Int, val status: Status)
fun function(...): Result {
// 计算
return Result(result, status)
}
// 现在,要使用此函数:
val (result, status) = function(...)由于数据类会自动声明 componentN() 函数,因此析构声明在这里有效。
你也可以使用标准类
Pair并让function()返回Pair<Int, Status>, 但通常将数据正确命名会更好。
示例:析构声明与映射 (Map)
遍历映射最优雅的方式可能就是这样:
for ((key, value) in map) {
// 使用 key 和 value 进行操作
}为了使此代码正常运行,你应该:
- 通过提供
iterator()函数将映射表示为一系列值。 - 通过提供函数
component1()和component2()将每个元素表示为一对值。
事实上,标准库提供了这样的扩展:
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()因此,你可以在带有映射的 for 循环中自由使用析构声明(以及数据类实例的集合或类似结构)。
用于未使用变量的下划线
如果在析构声明中不需要某个变量,可以使用下划线代替其名称:
val (_, status) = getResult()对于以这种方式跳过的组件,不会调用相应的 componentN() 运算符函数。
在 lambda表达式 中析构
你可以对 lambda表达式 的形参使用析构声明语法。 如果一个 lambda表达式 具有 Pair 类型(或 Map.Entry,或任何其他具有相应 componentN 函数的类型)的形参,你可以通过将它们放入圆括号中来引入多个新形参以代替单个形参:
map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }注意声明两个形参和声明一个析构对来代替一个形参之间的区别:
{ a -> ... } // 一个形参
{ a, b -> ... } // 两个形参
{ (a, b) -> ... } // 一个析构对
{ (a, b), c -> ... } // 一个析构对和另一个形参如果析构形参的某个组件未使用,可以使用下划线代替它,以避免去想变量名:
map.mapValues { (_, value) -> "$value!" }你可以为整个析构形参指定类型,也可以为特定组件单独指定类型:
map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }
map.mapValues { (_, value: String) -> "$value!" }基于名称的析构
Kotlin 支持基于名称的析构声明, 其中变量通过名称匹配属性,而不是由基于位置的析构中的 componentN() 函数定义的物理位置匹配。
有关基于名称的析构的更多信息,请参阅该功能的 KEEP。
在基于位置的析构中,变量对应于 componentN() 函数的顺序,例如:
data class User(val username: String, val email: String)
fun main() {
val user = User("alice", "[email protected]")
val (email, username) = user
println(email)
// alice
println(username)
// [email protected]
}在此示例中,由于析构依赖于 componentN() 函数的顺序,因此 email 接收了 username 的值,而 username 接收了 email 的值。
通过基于名称的析构,属性名称决定了提取哪些值,而不是 componentN() 函数的位置:
fun main() {
val user = User("alice", "[email protected]")
// 使用显式形式的基于名称的析构
(val mail = email, val name = username) = user
println(name)
// alice
println(mail)
// [email protected]
}基于名称的析构是实验性的。 当你启用此功能时,它还会引入一种使用方括号的基于位置的析构的新语法。 对于元素顺序很重要的类型,例如列表和其他有序集合,以及 Pair 或 Triple 等未命名的元组,请使用此语法:
val point = Pair(10, 20)
// 使用基于位置的析构
val [x, y] = point你可以通过 -Xname-based-destructuring 编译器选项控制编译器如何解释析构声明。
它具有以下模式:
only-syntax启用基于名称的析构的显式形式,而不改变现有析构声明的行为。name-mismatch当数据类中的基于位置的析构使用的变量名与属性名不匹配时,报告警告。complete启用带圆括号的简短形式基于名称的析构,并继续支持带方括号语法的基于位置的析构。
在启用
complete模式之前,请查看并解决在name-mismatch模式下报告的警告。 这些警告显示了编译器在complete模式下会以不同方式解释哪些析构声明,并包含相应重写这些声明的建议。
如果使用 complete 模式,带圆括号的简短形式析构语法将变量与属性名称匹配,而不是依赖位置:
val (email, username) = user要在项目中启用基于名称的析构,请将编译器选项添加到你的构建配置文件中:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xname-based-destructuring=only-syntax</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>