@zhongzilu
2019-01-28T12:14:37.000000Z
字数 4397
阅读 2830
你是否时常将一个对象中的多个属性赋值给多个变量呢? 你是否还傻傻地一个变量一个变量地手动赋值呢? 如果是的话, 那赶紧试试 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.nameval 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() = nameoperator fun component2() = age}// 方式二: 使用扩展函数进行重载operator fun Person.component1() = nameoperator 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 表达式;