Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 993
Kirill Avatar asked Jun 28 '19 08:06

Kirill


People also ask

How to pass array in function parameter Kotlin?

We can use the library function arrayOf() to create an array by passing the values of the elements to the function. Since Array is a class in Kotlin, we can also use the Array constructor to create an array. The constructor takes two parameters: The size of the array, and.

How to pass varargs in Kotlin?

In Kotlin, You can pass a variable number of arguments to a function by declaring the function with a vararg parameter. a vararg parameter of type T is internally represented as an array of type T ( Array<T> ) inside the function body.

What is spread operator in Kotlin?

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)


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)
}
like image 105
user2340612 Avatar answered Oct 16 '22 09:10

user2340612


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)
}
like image 42
ordonezalex Avatar answered Oct 16 '22 10:10

ordonezalex