Kotlin’s vararg and spread operator

What’s behind the scenes?

In some cases, you might need to define a function that should have a variable number of parameters; a couple of examples that you probably have encountered in Android are the printf(String format, Obj... args) function or the execute(Params... params) function from AsyncTask.

In this article we will start by looking at the basics about vararg and spread operator, and then explore some more elaborated scenarios — while analysing what happens behind the scenes.

vararg in Kotlin

Kotlin also supports declaring a function that can have a variable number of arguments. You can do that by prefixing parameter name with the vararg modifier: fun format(format: String, vararg args: Any)

vararg rules

In Java, the vararg parameter has to be the last one in the parameters list — so you can only have one vararg parameter. While in Kotlin, the vararg parameter doesn’t have to be the last one in the list, multiple vararg parameters are still prohibited.

Now let’s see how the decompiled corresponding Java source code looks like when declaring vararg parameters.

vararg param as the last one in list

Declaring vararg param as the last one in list, in a Kotlin function, looks like this:

fun format(format: String, vararg params: String)

and will be compiled into the corresponding Java code:

void format(@NotNull String format, @NotNull String... params)

Declaring params after the vararg

When vararg parameter is not the last one in list, like this:

fun format(format: String, vararg params: String, encoding: String)

it gets compiled into corresponding Java code:

void format(String format, String[] params, String encoding)

In conclusion, if vararg is not the last param it will be compiled as an array of parameter type.

Spread operator

“Wow!? Kotlin has pointers?” was my first reaction when I saw a Kotlin function called like this: format(output, *params) .

No, it’s not a pointer. It’s Kotlin’s spread operator — the operator that unpacks an array into the list of values from the array. It is needed when you want to pass an array as the vararg parameter.

If you try to call format function defined above like this:

val params = arrayOf("param1", "param2")

format(output, params)

it will fail with Type mismatch: inferred type is Array<String> but String was expected compilation error.

To fix this, you need to use spread operator to unpack params array into the corresponding values: format(output, *params)

Let’s see how the corresponding decompiled Java code looks like in this case:

String[] params = new String[]{"param1", "param2"};

format(output, (String[])Arrays.copyOf(params, params.length));

Combining spread values

When using the spread operator, you can pass additional values to the vararg parameter — or even combine multiple spread arrays in the same parameter:

val params = arrayOf("param1", "param2")

val opts = arrayOf("opts1", "opts2", "opts3")

format(output, *params, "additional", *opts)

The decompiled Java code is:

String[] params = new String[]{"param1", "param2"};

String[] opts = new String[]{"opts1", "opts2", "opts3"};

SpreadBuilder var10002 = new SpreadBuilder(3);

var10002.addSpread(params);

var10002.add("additional");

var10002.addSpread(opts);

this.format(output, (String[])var10002.toArray(new String[var10002.size()]));

Now, let’s take a look at arrayOf() definition:

public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>

That means that you can use spread operator to combine arrays into a new array:

val params = arrayOf("param1", "param2")

val opts = arrayOf("opts1", "opts2", "opts3") val allParams = arrayOf(*params, *opts)

As expected, the decompiled Java code is:

String[] params = new String[]{"param1", "param2"};

String[] opts = new String[]{"opts1", "opts2", "opts3"};

SpreadBuilder var10000 = new SpreadBuilder(2);

var10000.addSpread(params);

var10000.addSpread(opts);

String[] allParams = (String[])var10000.toArray(new String[var10000.size()]);

In conclusion, when combining spread values into a vararg parameter, a SpreadBuilder is used behind the scenes to gather all the values. The SpreadBuilder is then converted to an array when actually calling the function.

Hopefully, you have a better idea on how Kotlin handles vararg parameters and on how the corresponding Java code looks like when declaring and calling such functions.