[关闭]
@zhongzilu 2019-01-28T12:14:37.000000Z 字数 4397 阅读 2229

Kotlin 中的解构声明

你是否时常将一个对象中的多个属性赋值给多个变量呢? 你是否还傻傻地一个变量一个变量地手动赋值呢? 如果是的话, 那赶紧试试 Kotlin 的解构声明吧.

本篇文章重点讲解以下几个方面, 让大家对 Kotlin 中的解构声明, 以及如何运用有所了解.

  • 它是什么?
  • 它的优点是什么?
  • 它的原理是什么?
  • 如何使用?

它是什么?

有时候要获取一个对象的多个属性值,会定义多个变量来存放那些属性值,通常我们都是一个一个地手动赋值给变量, 这样子会很麻烦,而在 Kotlin 中有个很方便的用法,就是使用解构声明。

简单的来说, 解构声明就是将一个对象中的多个属性,按顺序对应到一组变量中. 这要如何理解呢? 我们来看例子就知道了.

假设我们有一个 Person 类, 表示一个人的基本信息.

  1. data class Person(val name: String, val age: Int)

此时要把 Person 对象中的 nameage 分别存放到变量 ab 中, 普通方式的写法如下:

  1. val person = Person("Tom", 18)
  2. // 分别赋值
  3. val a = person.name
  4. val b = person.age

看上去也不是很麻烦, 但如果是很多个这样的变量呢? 估计得疯掉, 如果换做 Kotlin 解构声明的写法, 那么代码可以如下:

  1. val (a, b) = Person("Tom", 18)

是不是很简洁? 原来还可以这么玩儿!

但是请注意⚠️: 上面普通方式的写法, 即便是交换变量 a 那行代码和变量 b 那行代码的顺序, 也不会改变它们的赋值结果, 但解构声明的写法当中, 如果交换 ab 的位置, 最终的赋值结果会刚好相反, 前者赋值 name的值, 后者赋值 age 的值, 这也就是前面为什么说, 解构声明是将一个对象中的多个属性,按顺序对应到一组变量中的原因.

它的优点是什么?

通过上面的例子可以看出, 解构声明的优点就是可以很方便的同时创建多个变量, 使得变量赋值更简洁, 但这只是其中之一, 它还有很多其他优点, 比如应用到 for循环中, 应用到 lambda表达式中等, 详情请看本文的 [ 如何使用 ] 部分的内容.

它的原理是什么?

解构声明的原理说来也简单, 就是原来我们自己手动赋值的代码, 现在由编译器来帮我们完成了, 只不过调用的不是属性的 get方法, 而是 componentN 方法函数, 就像下面这样:

  1. // 解构声明的代码
  2. val (a, b) = Person("Tom", 18)
  3. // 编译器编译后的代码
  4. val person = Person("Tom", 18)
  5. val a = person.component1()
  6. val b = person.component2()

编译器会根据数据类(data class)定义的属性顺序, 依此生成对应的componentN方法函数. 上面代码中, component1方法函数返回的值就是属性name的值, component2方法函数返回的值就是属性age的值.

在 Kotlin 中, 只有data class数据类才会自动生成相应的componentN方法函数, 普通的类是不会自动生成的, 因此普通类是不支持解构声明的, 那么怎么样才能使普通类也支持解构声明呢?

答案很简单, 自己实现一个componentN方法函数即可, 还记得 Kotlin 中操作符的重载吗? 我们可以使用operator关键字来重载componentN方法函数, 就像下面这样:

  1. // 方式一: 在类中进行重载
  2. class Person(val name: String, val age: Int) {
  3. // 重载componentN方法函数
  4. operator fun component1() = name
  5. operator fun component2() = age
  6. }
  7. // 方式二: 使用扩展函数进行重载
  8. operator fun Person.component1() = name
  9. operator fun Person.component2() = age

这下就让普通类也支持解构声明了, 在使用的时候和上面例子中的方式一样.

⚠️注意: 在重载componentN方法函数时, 必须按照顺序重载, 即必须从component1开始, 不可以在没有component1方法函数的情况下重载component2方法函数, 重载componentN方法函数的个数可以大于小于或等于类中属性的个数.

如何使用?

要想知道如何使用解构声明, 先从可以在哪些地方使用解构声明入手.接下来将从以下几个方面来讲解如何使用解构声明

  • 从函数中返回多个变量
  • 解构声明与 For 循环
  • 在 lambda 表达式中使用解构声明

从函数中返回多个变量

假设我们需要从⼀个函数返回两个变量。比如需要同时返回⼀个操作结果对象和⼀个表示操作状态的变量。在 Java 中, 我们通常会把这两个变量放到一个类中, 作为那个类的属性, 然后函数返回这个类的一个实例, 代码大概如下:

  1. // 结果类
  2. public class Result {
  3. public Object result; // 保存操作结果对象
  4. public int status; // 表示操作状态
  5. }
  6. // 操作方法函数
  7. public Result queryResult() {
  8. // ...各种操作代码
  9. return Result(result, status);
  10. }

然后在调用queryResult方法的地方, 代码可能如下:

  1. Result res = queryResult();
  2. Object obj = res.result;
  3. int status = res.status;

整个流程走下来非常的繁琐, 使用 Kotlin 中的解构声明就会简单很多, 代码如下:

  1. // 结果数据类
  2. data class Result(val result: Any?, val status: Int)
  3. // 操作方法函数
  4. fun queryResult(): Result {
  5. // ...各种操作代码
  6. return Result(result, status);
  7. }
  8. // 接收操作结果
  9. val (res, status) = queryResult()

你看, 是不是简洁了许多? 如果我们只想要接收status而不关心操作结果对象, 那么我们的接收代码可以这样写:

  1. val (_, status) = queryResult()

使用下划线 _ 来表示不需要的变量, 用 _ 表示的变量, 不会调用相应的 componentN 方法函数.

⚠️注意: 只在 Kotlin 版本1.1之后才能使用下划线来表示未使用的变量.

解构声明与 For 循环

我们在实际的 Java 项目中, 经常会存在遍历 Map 集合的情况. 在遍历时要同时获得 keyvalue, 编写的代码可能如下:

  1. // 第一种写法
  2. for (String key : map.keySet()){
  3. System.out.println("key= " + key);
  4. System.out.println("value= " + map.get(key));
  5. }
  6. // 第二种写法
  7. for (Map.Entry<String, String> entry : map.entrySet()) {
  8. System.out.println("key= " + entry.getKey());
  9. System.out.println("value= " + entry.getValue());
  10. }
  11. // 第三种写法
  12. Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
  13. while (it.hasNext()) {
  14. Map.Entry<String, String> entry = it.next();
  15. System.out.println("key= " + entry.getKey());
  16. System.out.println("value= " + entry.getValue());
  17. }

无论是哪一种写法, 感觉都挺麻烦, 然而使用 Kotlin 的解构声明, 就会简洁许多, 编写代码如下:

  1. for ((key, value) in map) {
  2. println("key= $key")
  3. println("value= $value")
  4. }

是不是很清晰明了呢? 之所以能这样使用, 是因为 Kotlin 标准库中已经提供了相应的扩展函数, 源代码如下:

  1. operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
  2. operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
  3. operator fun <K, V> Map.Entry<K, V>.component2() = getValue()

上面的写法是不是很熟悉? 这就是 Kotlin 中重载操作符的写法, 这里就不详细讲解了, 不清楚的戳这里再去看一遍原文吧.

在 lambda 表达式中使用解构声明

我们再回过头来看看上面 For 循环中的解构声明代码, 除了那种写法, 在 Kotlin 中, 还有一种 forEach 写法, 它其实就是一种 lambda 表达式 (关于 Kotlin 中 lambda 表达式的讲解, 请戳这里), 如下:

  1. map.forEach { entry ->
  2. println("key= ${entry.key}")
  3. println("value= ${entry.value}")
  4. }

上面的这种写法是最普通的写法, 当然你可以省略其中的 entry -> 代码, 省略之后, 将所有的 entry 换成 it即可.

由于 Kotlin 标准库中已经给 Map.Entry 扩展了 componentN 方法函数, 因此我们可以将上面 forEach 的写法改成解构声明的写法, 如下:

  1. map.forEach { (key, value) ->
  2. println("key= $key")
  3. println("value= $value")
  4. }

这样我们就不需要再通过 entry 去获取 keyvalue 的值了.

在 lambda 表达式中, 只要传递的参数或返回的值是 data class类型、 Pair 类型、Map.Entry类型, 又或者是自己重载了 componentN 方法函数的类型, 都可以使用解构声明的写法来简化参数值的获取.

下面是 lambda 表达式声明普通参数和解构对的示例:

  1. { a -> } // ⼀个参数
  2. { a, b -> } // 两个参数
  3. { (a, b) -> } // ⼀个解构对
  4. { (a, b), c -> } // ⼀个解构对以及其他参数

他们的区别就在于, 解构对是用弧括号 () 来包裹参数名的, 普通参数名不需要.

总结

好了, 最后给大家总结一下 Kotlin 中的解构声明. ①所谓的解构声明, 就是将一个可解构对象的多个属性, 按照顺序赋值给一组变量; ②一个可解构的对象, 必须重载 componentN 方法函数; ③Kotlin 中默认具有可解构的类型分别有 data class类型、Pair类型、Map.Entry类型; ④解构声明的优点是可以简化多个变量的赋值和获取操作, 使代码更简洁明了; ⑤它可以用在 For循环中, 也可以用来同时接收一个方法函数返回的多个结果, 方法函数可以是普通的方法函数, 也可以是 lambda 表达式;

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注