1. Overview

In this quick tutorial, we’re going to learn how to pass a variable number of arguments to functions in Kotlin. Also, we’ll see how Kotlin enables us to convert arrays to varargs.

Finally, we’ll take a look at how all of these are represented at the bytecode level.

2. Varargs

To pass a variable number of arguments to a function, we should declare that function with a vararg parameter:

fun sum(vararg xs: Int): Int = xs.sum()

This means that the sum() function can accept zero or more integers. For instance:

val zeroNumbers = sum()
assertEquals(0, zeroNumbers)

assertEquals(2, sum(2))
assertEquals(12, sum(2, 4, 6))

As shown above, we can pass zero or more parameters to a vararg argument.

Inside the function body, we can treat the vararg parameters as an array:

fun <T> printAll(vararg ts: T) {
    ts.forEach { println(it) }
}

For a vararg of a reference type, the vararg parameter will be treated as an Array of that reference type. For instance, in the above example, the ts parameter will be accessible as an Array<T> in the function body. In the following example, similarly, it will be an Array<String>:

fun printStrings(vararg vs: String) {
    vs.forEach { println(it) }
}

For primitive types, however, the vararg parameter will act like *Array specialized array typesFor instance, in the sum() function, the vararg parameter is an IntArray in the function body.

3. Limitations

Each function can have at most one vararg parameter. If we declare a function with more than one vararg parameter, the Kotlin compiler will fail with an error:

Kotlin: Multiple vararg-parameters are prohibited

Unlike many other programming languages, it’s not necessary to declare the vararg parameter as the last parameter:

fun createUser(vararg roles: String, username: String, age: Int) {
    // omitted
}

However, when the vararg is not the last declared parameter, we should pass other parameters by name to avoid the ambiguity:

createUser("admin", "user", username = "me", age = 42)

Otherwise, the compiler will fail with an error.

4. Spread Operator

Sometimes we have an existing array instance in Kotlin, and we want to pass it to a function accepting a vararg. In those situations, to decompose the array to a vararg, we can use the spread operator:

val numbers = intArrayOf(1, 2, 3, 4)
val summation = sum(*numbers)
assertEquals(10, summation)

The “*” behind the numbers array variable is the spread operator.

5. Bytecode Representation

Under-the-hood, Kotlin translates its varargs to Java varargs. To verify this, let’s compile the Kotlin source code via kotlinc:

$ kotlinc Vararg.kt

After that, we can take a peek at the generated bytecode via javap:

$ javap -c -p -v com.baeldung.varargs.VarargKt
  public static final int sum(int...);
    descriptor: ([I)I
    flags: (0x0099) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_VARARGS
  // truncated

As shown above, the vararg parameter is defined as a vararg in Java. Also, the ACC_VARARGS flag specifies that the function accepts a variable-length parameter.

Moreover, when using the spread operator:

val numbers = intArrayOf(1, 2)
sum(*numbers)

Kotlin will use the Arrays.copyOf(array, length) method to first create a copy of the spreading array. Then it will pass the new array as the vararg parameter:

12: aload_0 // loads the int[]
13: dup
14: arraylength
15: invokestatic  #71   // Method java/util/Arrays.copyOf:([II)[I
18: invokestatic  #72   // Method sum:([I)I

Index 13 and 14 are loading two parameters into the operand stack and then passing them to the Arrays.copyOf(int[], int) method. Basically, it copies all elements from the original array to a new one and passes the copied one to the sum() function.

In Java, vararg parameters must be declared as the last parameter. So, when we declare a vararg parameter in any position except the last one in Kotlin:

fun createUser(vararg roles: String, username: String, age: Int) {
    // omitted
}

The compiler translates that particular vararg into a simple array:

public static final void createUser(java.lang.String[], java.lang.String, int);
    descriptor: ([Ljava/lang/String;Ljava/lang/String;I)V
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL

As we can see, the Kotlin compiler translated the “vararg roles: String” to a “String[]” instead of the expected “String…” construct.

6. Conclusion

In this short tutorial, we learned how vararg parameters work in Kotlin. In addition, we saw how to convert Kotlin’s existing array types to varargs using the spread operator.

As usual, all the examples are available over on GitHub.

Comments are closed on this article!