Unpacking the Power of Destructuring Declarations in Kotlin

Photo by Louis Tsai on Unsplash

Unpacking the Power of Destructuring Declarations in Kotlin

Kotlin is a powerful programming language that offers a variety of features to make code more readable and maintainable. One of these features is destructuring declarations, which allows developers to unpack the contents of an object into separate variables. In this post, we will take a closer look at destructuring declarations in Kotlin and explore how they can be used to make your code more concise and readable.

Destructuring Data classes

Destructuring declarations are a concise way to extract multiple properties of an object into separate variables. This is done using the component1(), component2(), etc functions that the object should declare. For example, the following code uses destructuring declarations to extract the x and y properties of a Point object:

data class Point(val x: Int, val y: Int)

val point = Point(1, 2)
val (x, y) = point
println("x = $x, y = $y")

This code is equivalent to the following code without destructuring declarations:

val point = Point(1, 2)
val x = point.x
val y = point.y
println("x = $x, y = $y")

What's under the hood

Let’s take a look at a decompiled data class to see what’s going on. To see the Java decompiled code, go to Tools -> Kotlin -> Show Kotlin Bytecode then press the Decompile button.

public final class Point {
   private final int x;
   private final int y;

   public final int getX() {
      return this.x;
   }

   public final int getY() {
      return this.y;
   }

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public final int component1() {
      return this.x;
   }

   public final int component2() {
      return this.y;
   }
}

We see that functions called componentN are generated for every property declared in the primary constructor, where N is the index of the field in the primary constructor

Destructuring Collections and Loops

Destructuring declarations can also be used with collections, such as lists and maps. For example, the following code uses destructuring declarations to extract the first and last elements of a list:

val numbers = listOf(1, 2, 3, 4, 5)
val (first, last) = numbers
println("first = $first, last = $last")

This code is equivalent to the following code without destructuring declarations:

val numbers = listOf(1, 2, 3, 4, 5)
val first = numbers[0]
val last = numbers[numbers.size - 1]
println("first = $first, last = $last")

One of the main advantages of destructuring declarations is that they make code more readable and maintainable. By extracting multiple properties of an object into separate variables, you can avoid having to access properties through the object, making the code more self-explanatory. Additionally, destructuring declarations can make it easier to work with collections and make it easier to understand the intent of the code.

In addition to the examples shown above, destructuring declarations can also be used in for loops. This can be useful for iterating over collections of objects and extracting the properties you need.

val list = listOf(Point(1,2), Point(2,3), Point(3,4))
for((x,y) in list){
    println("x = $x, y = $y")
}

Underscore and Destructuring in Lambdas

In case we don’t need all values obtained in a destructuring declaration, we can use underscore instead of the variable name:

val (x,_) = point

You can use the destructuring declarations syntax for lambda parameters. If a lambda has a parameter of the Pair type (or Map.Entry, or any other type that has the appropriate componentN functions), you can introduce several new parameters instead of one by putting them in parentheses:

map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

Note the difference between declaring two parameters and declaring a destructuring pair instead of a parameter:

{ a -> ... } // one parameter
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // a destructured pair
{ (a, b), c -> ... } // a destructured pair and another parameter

Destructuring for classes you don’t own

Kotlin allows you to implement destructuring for classes you don’t own via extension functions. For example, Map.Entry is just an interface and by default it does not support destructuring. To overcome this, component1() and component2() functions were created, which return the key and value of Map.Entry

Conclusion

In conclusion, destructuring declarations in Kotlin are a powerful feature that can make your code more readable and maintainable. By allowing you to extract multiple properties of an object into separate variables, they can help you write more concise and self-explanatory code. With destructuring declarations, you can easily extract the properties you need from objects and collections, making it easier to work with data in your code.

References