Why Kotlin's Type's vararg is treated as Array<out Type> and not Array<Type>

Viewed 3186

The following method gets compiled in Java:

public class Main {
    public static void main(String[] args) {
        varargMethod(1, 2.0);
    }

    static void varargMethod(Number... va) {
        arrayMethod(va);
    }

    static void arrayMethod(Number[] arr) {
        for (Number number : arr) {
            System.out.println(number);
        }
    }
}

If I try to write similar code in Kotlin i get type mismatch error:

fun main() {
    varargFun(1, 2.0)
}

fun varargFun(vararg va: Number) {
    arrayFun(va) // Error:(6, 14) Kotlin: Type mismatch: inferred type is Array<out Number> but Array<Number> was expected
}

fun arrayFun(arr: Array<Number>) {
    arr.forEach {
        println(it)
    }
}

I expected va to be of type Array<String>, but it is Array<out String>. If I cast it: va as Array<Number>, I get a warning:

Warning:(6, 21) Kotlin: Unchecked cast: Array to Array

How am I supposed to pass vararg as an Array to another function without getting warning and errors?

2 Answers

The difference is that in Java arrays are covariant, i.e. the following is valid:

public static void main(String[] args) {
    Number[] numbers = new Number[0];
    Integer[] ints = new Integer[0];

    numbers = ints;
}

However, arrays are not covariant in Kotlin, i.e. the following gives a compilation error:

var numbers: Array<Number> = arrayOf()
val ints: Array<Int> = arrayOf()

numbers = ints // error: required Array<Number>, found Array<Int>

However you can declare the array is a producer (i.e. you promise you'll never insert anything inside it; the compiler will make sure of that) with the keyword out. That makes the array covariant, i.e. the following is valid:

var numbers: Array<out Number> = arrayOf() // we will only extract Numbers out of this array
val ints: Array<Int> = arrayOf()

numbers = ints // this is ok

Given that, if vararg va: Number was not treated as a Array<out Number>, then you could have called your method only with Number objects and not with its subclasses. I.e., the following would fail:

fun main() {
    varargFun(arrayOf<Int>(1, 2)) // error: required Array<Number>, found Array<Int>
}

fun varargFun(va: Array<Number>) {
    arrayFun(va)
}

But again, with an out (which is what vararg does), it magically works:

fun main() {
    varargFun(arrayOf<Int>(1, 2))
}

fun varargFun(va: Array<out Number>) {
    arrayFun(va)
}

This is covered in the Kotlin documentation:

Inside a function a vararg-parameter of type T is visible as an array of T, i.e. the [...] variable in the example above has type Array<out T>.

The solution to your problem is simple: ignore Kotlin's guard rails, and copy the arguments.

fun varargFun(vararg va: Number) {
    val copy = arrayOf(*va)
    arrayFun(copy)
}
Related