@zhongzilu
2019-01-28T20:14:37.000000Z
字数 4397
阅读 2500
你是否时常将一个对象中的多个属性赋值给多个变量呢? 你是否还傻傻地一个变量一个变量地手动赋值呢? 如果是的话, 那赶紧试试 Kotlin 的解构声明吧.
本篇文章重点讲解以下几个方面, 让大家对 Kotlin 中的解构声明, 以及如何运用有所了解.
- 它是什么?
- 它的优点是什么?
- 它的原理是什么?
- 如何使用?
有时候要获取一个对象的多个属性值,会定义多个变量来存放那些属性值,通常我们都是一个一个地手动赋值给变量, 这样子会很麻烦,而在 Kotlin 中有个很方便的用法,就是使用解构声明。
简单的来说, 解构声明就是将一个对象中的多个属性,按顺序对应到一组变量中. 这要如何理解呢? 我们来看例子就知道了.
假设我们有一个 Person
类, 表示一个人的基本信息.
data class Person(val name: String, val age: Int)
此时要把 Person
对象中的 name
和 age
分别存放到变量 a
和 b
中, 普通方式的写法如下:
val person = Person("Tom", 18)
// 分别赋值
val a = person.name
val b = person.age
看上去也不是很麻烦, 但如果是很多个这样的变量呢? 估计得疯掉, 如果换做 Kotlin 解构声明的写法, 那么代码可以如下:
val (a, b) = Person("Tom", 18)
是不是很简洁? 原来还可以这么玩儿!
但是请注意⚠️: 上面普通方式的写法, 即便是交换变量 a
那行代码和变量 b
那行代码的顺序, 也不会改变它们的赋值结果, 但解构声明的写法当中, 如果交换 a
和 b
的位置, 最终的赋值结果会刚好相反, 前者赋值 name
的值, 后者赋值 age
的值, 这也就是前面为什么说, 解构声明是将一个对象中的多个属性,按顺序对应到一组变量中的原因.
通过上面的例子可以看出, 解构声明的优点就是可以很方便的同时创建多个变量, 使得变量赋值更简洁, 但这只是其中之一, 它还有很多其他优点, 比如应用到 for
循环中, 应用到 lambda
表达式中等, 详情请看本文的 [ 如何使用 ] 部分的内容.
解构声明的原理说来也简单, 就是原来我们自己手动赋值的代码, 现在由编译器来帮我们完成了, 只不过调用的不是属性的 get
方法, 而是 componentN
方法函数, 就像下面这样:
// 解构声明的代码
val (a, b) = Person("Tom", 18)
// 编译器编译后的代码
val person = Person("Tom", 18)
val a = person.component1()
val b = person.component2()
编译器会根据数据类(data class)
定义的属性顺序, 依此生成对应的componentN
方法函数. 上面代码中, component1
方法函数返回的值就是属性name
的值, component2
方法函数返回的值就是属性age
的值.
在 Kotlin 中, 只有data class
数据类才会自动生成相应的componentN
方法函数, 普通的类是不会自动生成的, 因此普通类是不支持解构声明的, 那么怎么样才能使普通类也支持解构声明呢?
答案很简单, 自己实现一个componentN
方法函数即可, 还记得 Kotlin 中操作符的重载吗? 我们可以使用operator
关键字来重载componentN
方法函数, 就像下面这样:
// 方式一: 在类中进行重载
class Person(val name: String, val age: Int) {
// 重载componentN方法函数
operator fun component1() = name
operator fun component2() = age
}
// 方式二: 使用扩展函数进行重载
operator fun Person.component1() = name
operator fun Person.component2() = age
这下就让普通类也支持解构声明了, 在使用的时候和上面例子中的方式一样.
⚠️注意: 在重载componentN
方法函数时, 必须按照顺序重载, 即必须从component1
开始, 不可以在没有component1
方法函数的情况下重载component2
方法函数, 重载componentN
方法函数的个数可以大于小于或等于类中属性的个数.
要想知道如何使用解构声明, 先从可以在哪些地方使用解构声明入手.接下来将从以下几个方面来讲解如何使用解构声明
- 从函数中返回多个变量
- 解构声明与 For 循环
- 在 lambda 表达式中使用解构声明
假设我们需要从⼀个函数返回两个变量。比如需要同时返回⼀个操作结果对象和⼀个表示操作状态的变量。在 Java 中, 我们通常会把这两个变量放到一个类中, 作为那个类的属性, 然后函数返回这个类的一个实例, 代码大概如下:
// 结果类
public class Result {
public Object result; // 保存操作结果对象
public int status; // 表示操作状态
}
// 操作方法函数
public Result queryResult() {
// ...各种操作代码
return Result(result, status);
}
然后在调用queryResult
方法的地方, 代码可能如下:
Result res = queryResult();
Object obj = res.result;
int status = res.status;
整个流程走下来非常的繁琐, 使用 Kotlin 中的解构声明就会简单很多, 代码如下:
// 结果数据类
data class Result(val result: Any?, val status: Int)
// 操作方法函数
fun queryResult(): Result {
// ...各种操作代码
return Result(result, status);
}
// 接收操作结果
val (res, status) = queryResult()
你看, 是不是简洁了许多? 如果我们只想要接收status
而不关心操作结果对象, 那么我们的接收代码可以这样写:
val (_, status) = queryResult()
使用下划线 _
来表示不需要的变量, 用 _
表示的变量, 不会调用相应的 componentN
方法函数.
⚠️注意: 只在 Kotlin 版本1.1之后才能使用下划线来表示未使用的变量.
我们在实际的 Java 项目中, 经常会存在遍历 Map
集合的情况. 在遍历时要同时获得 key
和 value
, 编写的代码可能如下:
// 第一种写法
for (String key : map.keySet()){
System.out.println("key= " + key);
System.out.println("value= " + map.get(key));
}
// 第二种写法
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey());
System.out.println("value= " + entry.getValue());
}
// 第三种写法
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey());
System.out.println("value= " + entry.getValue());
}
无论是哪一种写法, 感觉都挺麻烦, 然而使用 Kotlin 的解构声明, 就会简洁许多, 编写代码如下:
for ((key, value) in map) {
println("key= $key")
println("value= $value")
}
是不是很清晰明了呢? 之所以能这样使用, 是因为 Kotlin 标准库中已经提供了相应的扩展函数, 源代码如下:
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()
上面的写法是不是很熟悉? 这就是 Kotlin 中重载操作符的写法, 这里就不详细讲解了, 不清楚的戳这里再去看一遍原文吧.
我们再回过头来看看上面 For
循环中的解构声明代码, 除了那种写法, 在 Kotlin 中, 还有一种 forEach
写法, 它其实就是一种 lambda 表达式 (关于 Kotlin 中 lambda 表达式的讲解, 请戳这里), 如下:
map.forEach { entry ->
println("key= ${entry.key}")
println("value= ${entry.value}")
}
上面的这种写法是最普通的写法, 当然你可以省略其中的 entry ->
代码, 省略之后, 将所有的 entry
换成 it
即可.
由于 Kotlin 标准库中已经给 Map.Entry
扩展了 componentN
方法函数, 因此我们可以将上面 forEach
的写法改成解构声明的写法, 如下:
map.forEach { (key, value) ->
println("key= $key")
println("value= $value")
}
这样我们就不需要再通过 entry
去获取 key
和 value
的值了.
在 lambda 表达式中, 只要传递的参数或返回的值是 data class
类型、 Pair
类型、Map.Entry
类型, 又或者是自己重载了 componentN
方法函数的类型, 都可以使用解构声明的写法来简化参数值的获取.
下面是 lambda 表达式声明普通参数和解构对的示例:
{ a -> } // ⼀个参数
{ a, b -> } // 两个参数
{ (a, b) -> } // ⼀个解构对
{ (a, b), c -> } // ⼀个解构对以及其他参数
他们的区别就在于, 解构对是用弧括号 ()
来包裹参数名的, 普通参数名不需要.
好了, 最后给大家总结一下 Kotlin 中的解构声明. ①所谓的解构声明, 就是将一个可解构对象的多个属性, 按照顺序赋值给一组变量; ②一个可解构的对象, 必须重载 componentN
方法函数; ③Kotlin 中默认具有可解构的类型分别有 data class
类型、Pair
类型、Map.Entry
类型; ④解构声明的优点是可以简化多个变量的赋值和获取操作, 使代码更简洁明了; ⑤它可以用在 For
循环中, 也可以用来同时接收一个方法函数返回的多个结果, 方法函数可以是普通的方法函数, 也可以是 lambda 表达式;