Upcoming and OnDemand Webinars View full list

Kotlin Destructuring Declarations and ComponentN

Rafael Moreno Cesar

Kotlin magic shorthand sytax is everywhere! Kotlin provides some useful syntactic sugar to
assign values from your structures to multiple variables at once like in the example below:

val threeStrings = listOf("one", "two", "three")
val (first, second) = exampleList

In this article we’ll go through how that’s possible in Kotlin, and take a look at two
very common places this destructuring syntax can be used.

Destructuring Collections

Using Kotlin’s destructuring syntax on collections, you can avoid assigning the same variables
on mutliple lines like you’d be doing in the example below

Example 1.

val threeStrings = listOf("one", "two", "three")
val first = exampleList[0]
val second = exampleList[1]

With such a handy syntax, you might want to go crazy and pull out all of the values of one
not-such-a-large-collection,

Example 2.

val sixStrings = listOf("one", "two", "three", "four", "five", "six")
val (first, second, third, fourt, fifth, sixth) = exampleList

but you’ll notice if you try the above example, the compiler actually complains and prompts
you with an error of Kotlin: Destructuring declaration initializer of type List<String> must have a 'component6()' function.

What gives!? Why doesn’t the code compile, and why does the error prompt you to call some component6()
function if it worked for getting the elements you wanted in your previous example? To see
what’s going on, let’s look at what the Kotlin compiler is doing when it compiles the code down in Example 1.

val first = threeStrings.component1
val second = threeStrings.component2

Here you see your first clue into figuring out why you get the mysterious error.
The first thing you might note here is that Kotlin has converted your short hand syntax into
a long form. However, instead of indexing the list in a way you might be used to seeing, such
as threeStrings[0], it’s accessing a pair of properties on your threeStrings called
component1 and component2. If you actually go ahead and look at the implementation of
your list of type List<Int>, you might notice that neither component1 or component2 are defined as properties in the class.

What the creators of Kotlin did was actually implement 5 extension functions on many of the classes in the Collections.kt library that we commonly use.

operator fun <T> List<T>.component1(): T
operator fun <T> List<T>.component2(): T
operator fun <T> List<T>.component3(): T
operator fun <T> List<T>.component4(): T
operator fun <T> List<T>.component5(): T

Under the hood, Kotlin is actually calling the component function up until the fifth element
that you want to pull out of your collection. This is why in Example 2, you were getting this
error when trying to pull six elements out of your list. Kotlin: Destructuring declaration
initializer of type List<String> must have a 'component6()' function.
Kotlin did not
actually implement an extension function to pull out component6().

If you look at the explicit documentation for component1() with the only difference between itself, you can actually see that that it really is implemented on Kotlin Collections not just a List like in our example:

operator fun <T> Array<out T>.component1(): T
operator fun ByteArray.component1(): Byte
operator fun ShortArray.component1(): Short
operator fun IntArray.component1(): Int
operator fun LongArray.component1(): Long
operator fun FloatArray.component1(): Float
operator fun DoubleArray.component1(): Double
operator fun BooleanArray.component1(): Boolean
operator fun CharArray.component1(): Char
operator fun <T> List<T>.component1(): T

component2()component5() look the same as component1()

Destructuring Data classes

Like with collections, you can also destructure the properties in your data classes

data class Numbers(val first: String, 
                val second: String, 
                val third: String, 
                val fourth: String, 
                val fifth: String,
                val sixth: String)
                
val (one, two, three, four, five, six) = Numbers("one", "two", "three", "four", "five", "six")

With your data class, you can actually compile this code even though you are trying to pull
out 6 elements. Why then, were you given an error trying to do the same thing with a
collection? Destructuring data class works a little differently than destructuring
collections. When you look at what Kotlin is doing when this is compiled, you’ll notice
that it actually added a component for each property that was passed into the constructor
of your Numbers data class.

Data classes in Kotlin actually define a series of componentN functions at compile time,
where N is the amount of values you’re passing into the constructor. Because these aren’t
extension functions that are part of the Kotlin standard library, but functions that are
generated by Kotlin, if you were to decompile this data class you can actually see all the
generated componentN functions inside of the class.

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

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

public final int component3() {
    return this.third
}

Conclusion

You now know about what’s happening with componentN under the hood.
We’ve ruined the magic and shown that Kotlin is just doing work under the hood to allow us
to pull out the values that we want. If you have any questions, I’d love to hear from you.
Feel free to reach out at @rmorenocesar, or comment below!

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project