Short and easy to use, default arguments allow you to implement function overloads without the boilerplate. Like many Kotlin features, this can feel like magic. Are you curious to learn its secrets? Read on to find out how default arguments work under the hood.
If you need to overload a function, instead of implementing the same function multiple times, you can use default arguments:
Default arguments can be applied to constructors as well:
By default, Java doesn’t recognize the default value overload:
To instruct the compiler to generate the overload methods, use the
@JvmOverloads annotation on your Kotlin function:
Under the hood
Let’s look at the Java decompiled code to see what the compiler generates for us:
Tools -> Kotlin -> Show Kotlin Bytecode then press the
We see that the compiler generates 2 functions:
play— that has 1 parameter:
Toyand is called when default arguments are not used
- a synthetic method
play$default— that has 3 parameters:
Object; it’s called whenever the default arguments are used. The
Objectparameter is always
nullbut the value of the int differs. Let’s see how!
The int parameter
The value of the int parameter of
play$default is computed based on the number and the index of the arguments that have a default argument passed in. Based on the value of this parameter, the Kotlin compiler knows with which parameters to call the play function.
play() example call, the argument at index 0 is using the default argument. Therefore,
play$default is called with
int var1 = 2⁰:
play$default((Toy)null, 1, (Object)null);
play$default implementation then knows that the value of
var0 should be replaced with the default value.
Let’s take a more complex example to see how the int parameter behaves. Let’s expand our
play function and, at the call site, use the default argument for
Let’s see what happened in the decompiled code:
We see that now, our int parameter is 5. Here’s how this was computed: the parameters at positions 0 and 2 are using the default argument so
var3 = 2⁰ + 2² = 5. In a bitwise
& operation, the parameters get evaluated as such:
var3 & 1 != 0is
var0 = goodDoggo
var3 & 2 != 0is
var1is not replaced
var3 & 4 != 0is
var2 = SqueakyToy
Based on the bitmask applied to
var3, the compiler can compute which parameters should be replaced with default values.
The Object parameter
In the examples above you might have noticed that the value of the
Object parameter is always null and that it’s actually never used in the
play$default function. This parameter is connected to supporting default values in overriding functions.
Default arguments and Inheritance
What happens when we want to override a function with default arguments?
Let’s change the example above and:
- Create a new
PlayfulDoggoclass, that extends
When we want to set a default value in PlayfulDoggo.play we see that we are not allowed: An overriding function is not allowed to specify default values for its parameters
If we remove the
override and we check the decompiled code,
PlayfulDoggo.play() looks like this:
Does this mean that super calls with default arguments will be supported in the future? We’ll just have to wait and see.
For constructors, the decompiled Java code has only one difference. Let’s take a look:
Constructors also create a synthetic method but, instead of the
Object used in functions, constructors use a
DefaultConstructorMarker which is called with
Secondary constructors with default arguments will also generate another synthetic method using a
DefaultConstructorMarker, like primary constructors:
Simple and sweet, default arguments decrease the amount of boilerplate code we need to write when dealing with overloaded methods, allowing us to set default values for our parameters. As is the case for a lot of Kotlin keywords, when can understand their magic by peeking at the code it writes for us. Check out our other Kotlin Vocabulary posts for more.