Sometimes we find ourselves in a situation where we want to return multiple values as the result of a method call. Some programming languages have such functionality built-in and allow us to assign the returned values directly back into individual variables. Like for example in Go:
|
|
In Java, this is unfortunately not so easily possible. Because the Java virtual machine supports only one return value. To return more than one value, we would have to create a container object that holds the individual values. Unfortunately, this bloats the code enormously. The following code is the Java equivalent of the Go code above:
|
|
If we now migrate this Java code to Kotlin, we have already saved a massive amount of code due to the lightweight nature of Kotlin alone:
|
|
But we still have the inconvenience of the intermediate step via the container object to access the individual values.
Destruction Declaration
Kotlin has a solution for this: If we now migrate the ValuesContainer
class into a data class, by simply putting the data
keyword in front of class
we can take advantage of Kotlin’s destruction declarations functionality. This allows us to split the returned container object into its individual values and assign them directly to separated new variables:
|
|
The types of each returned variable are automatically derived from their property types in the data class. However, we can also specify them explicitly:
|
|
Common Container Classes
Due to Kotlin’s philosophy to simplifying constructs that frequently occur in the real world, the standard library offers container classes for up to 5 elements.
Pair and Triple
We could use kotlin.Pair
and kotlin.Triple
for two or three elements. With this, we are now able to reduce the example code even further by replacing our container class with the built-in one:
|
|
Arrays and Lists
Similarly, we can use destruction declarations also for Arrays and Lists (but not for Sets and Maps because they have no deterministic order):
|
|
However, this mechanism works out-of-the-box only for up to five variables:
|
|
However, we should note that destruction declarations on Arrays and Lists open up a potential failure source: An ArrayIndexOutOfBoundsException
is thrown at runtime if there are not enough elements in the array as variables are defined.
Extending Destruction Declarations to all Java Classes
If we create new container classes ourselves or use the existing ones from the standard library, it is easy to use the multiple return value feature.
When we create new container classes or use the existing ones, it is easy to use the multiple return values feature. But since we don’t want to reinvent the wheel, we often use existing, external Java classes, which do not fulfill the conditions for destruction declarations.
For example, let’s take an instance of a java.time.LocalDate
, which contains a day, month, and year value. It would be nice to split the object into its three values:
|
|
We can do this by taking a closer look at how the underlying mechanism of destruction declarations works: The mapping of each variable to the corresponding property in the instance is an operator with a function name component*
where the postfix *
is the number of the variable position, starting at 1
. With extension functions, we can now define the operators for the destruction declaration variables for every Java class we want. For our java.time.LocalDate
example, this would be:
|
|
Handling Null Values
The individual values of a container object can have nullable types:
|
|
But the return type itself must not be nullable. Which would otherwise lead to a compile error:
|
|
Ignoring Values
If we are not interested in certain values, we can either omit them if they occur at the end:
|
|
Or we can use the Kotlin keyword _
to ignore them:
|
|
Under the Hood
As pointed out at the beginning, the Java virtual machine does not actually support multiple return values. And since Kotlin is also compiled to Java byte code and runs on the JVM, it should not support it either. So how did the Kotlin architects make this functionality possible nevertheless?
A look into the byte code tells us the secret. The line val (first, second) = values()
, where the method returns type is Pair<Int, Int>
, is compiled to the following byte code:
|
|
If we decompile this byte code again, we would get the following Java code:
|
|
So we see that the magic is nothing more than assigning the container object into a variable and then assigning its individual values into individual variables. Kotlin just encapsulates the boilerplate code for us here that we would have to write otherwise.