Kotlin的解构技巧

1,100 阅读4分钟

Kotlin支持一种非常方便的方法读取一个对象的属性。类似这种写法:

val (name, age) = person

如上,我们可以方便的获取person对象中的属性。

当然,这样的特性不是天生就有的,我们可能需要一些额外的工作。
跟着这边文章,我们来看一下,如何编写Kotlin的解构代码。

假设我们创建一个Pair对象:

val pair = Pair("Bob", 12)

备注,创建Pair有一个更加优雅的写法:

val pair = "Bob" to 12

Great!
接着,我们想获取对象pair中的两个值:

val pair = "Bob" to 12
val name = pair.first
val age = pair.second

非常正确。
不过借助于Kotlin的解构,我们还能写的更加简洁:

val pair = "Bob" to 12
val (name, age) = pair

解构声明时,我们使用一对括号,放入我们的变量,然后直接=一个对象,就是一段解构声明。Kotlin会自动按照类变量声明的顺序,将相关的值取出赋值给=左边的变量。例如这里的Pair类,我们在创建的时候,先声明了Bob,然后声明了12,这样在解构时,第一个变量就会给予值Bob,第二个变量就会给予12这个值。

除了Pair类,Kotlin的集合也支持解构声明:

val alpha = listOf("A", "B", "C")
val (a, b, c) = alpha
// a = "A" b = "B" c = "C"

此时,变量abc均赋值了列表中正确的值。这里有个小问题,如果我们变量的数量多于列表的大小呢? 例如:

val (a, b, c, d) = alpha

在执行到这段代码的时候,我们会得到一个异常:
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
所以,在使用列表解构的时候,要非常小心列表的大小问题,不然会直接收获一个异常。

同样,Map类型也支持解构:

val persons = mapOf(
    "Bob" to 12,
    "Josh" to 14,
    "Linda" to 13
)

for ((name, age) in persons) {
    println("$name is $age year(s) old")
}

输出为:

Bob is 12 year(s) old
Josh is 14 year(s) old
Linda is 13 year(s) old

在循环时,我们使用for ((name, age) in persons)的写法,让Kotlin自动帮我们执行Map的遍历与键值对的解构,循环中,我们就可以直接使用nameage了。

Kotlindata class,为我们自动提供了解构的特性。

fun main() {
    val linda = Person("Linda", 13)
    val (name, age) = linda
}

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

我们声明了一个Person类,该类是data class,提供两个属性nameage,我们可以直接对该类的对象使用解构声明val (name, age) = linda,此时,我们能够得到正确的值。
但是关于data class的解构有一个小陷阱:Kotlin在支持data class解构时,是按照变量声明顺序支持的,也就是说,data class的变量声明顺序,直接影响到了解构的结果。
例如:

fun main() {
    val linda = Person("Linda", 6, 13)
    val (name, age) = linda
    println(age)
}

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

上述代码我们稍作修改,在age前面添加一个属性grade,而我们的解构声明并没有做相应的改变,此时,变量age所对应的值,已经不再是Person.age了,而是Person.grade,所以大家在data class中使用解构时需要额外注意。有两种方法可以防止这样的错误情况发生:

  • 从不使用data class的解构声明。
  • data class在新添加属性的时候,一定要在后添加,而不能在中间或前面。

Kotlin自动提供的解构声明,大概就是如上几种情况了,那么如果我们自己声明一个普通类,如何才能支持解构呢?

class Student(val name: String, val age: Int)

我们创建了这样一个类,想支持解构。
其实非常简单,Kotlin为我们提供了相关的操作符重载函数componentN()function(N >= 1):

class Student(val name: String, val age: Int) {
    operator fun component1(): String {
        return name
    }

    operator fun component2(): Int {
        return age
    }
}

添加两个操作符重载函数,即可实现普通类的解构:

val linda = Student("Linda", 13)
val (name, age) = linda

It works!

当然,普通类的解构,完全取决于我们的componentN()function返回什么。所以,在使用普通类解构时,我们也有一个要求防止后续代码更改带来的问题:

  • 已经有的解构声明,不做改动。 以Student举例,后续如果我们需要为新的属性提供解构的话,那么就添加新的component3()component4(),现有存在的componentN()function就不要做任何改动了。

OK,关于Kotlin的解构技巧就讲到这里,希望对大家有所帮助。