Don’t argue with default arguments

Florina Muntenescu
Android Developers
Published in
3 min readOct 8, 2020

--

Kotlin Vocabulary

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.

Basic usage

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:

Java interop

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 Decompile button.

Functions

We see that the compiler generates 2 functions:

  • play — that has 1 parameter: Toy and is called when default arguments are not used
  • a synthetic method play$default — that has 3 parameters: Toy, an int and an Object; it’s called whenever the default arguments are used. The Object parameter is always null but 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.

In our 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);

The 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 doggo and toy:

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 != 0 is true so var0 = goodDoggo
  • var3 & 2 != 0 is false so var1 is not replaced
  • var3 & 4 != 0 is true so 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:

  • Make play an open function of Doggo and Doggo an open class.
  • Create a new PlayfulDoggo class, that extends Doggo and overrides play

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.

Constructors

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 null:

Secondary constructors with default arguments will also generate another synthetic method using a DefaultConstructorMarker, like primary constructors:

Conclusion

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.

--

--