Search code examples
arraysgenericskotlincastingany

Why can't Kotlin implicitly upcast Array of String into Array of Any?


The following code implicitly upcasts a String into Any.

    val s = "some string"
    val upcasted: Any = s

However, the following does not compile (type mismatch):

    val s = arrayOf("some string")
    val upcasted: Array<Any> = s

You can successfully cast to Array<Any> as shown:

    val s = arrayOf("some string")
    val upcasted: Array<Any> = s as Array<Any>

However, this gives the warning "Unchecked cast: Array<String> to Array<Any>". The same logic does appear to work with Lists, so is the internal implementation of Array incompatible with this type of casting? Perhaps it because of the in-memory representation of Arrays?


Solution

  • You can't safely cast a type to a supertype or subtype like this, because an Array<String> does not qualify as an Array<Any>. If you tried to put a Double in your supposed Array<Any>, it would throw an exception, because the actual type is a String array. Arrays are a special case of generics that don't have type erasure, so they are locked to the type they were instantiated with.

    What you could do is cast it to an Array<out Any>, because you can safely use it that way. You can pull Strings out of the array and they qualify as instances of Any. The reverse is not true. You can't put any instance of Any and its subclasses into a String array.

    When it comes to List, you can cast it to a List with the supertype type and you don't even have to manually cast it. It's safe so the cast can be done implicitly.

    val s = listOf("some string")
    val upcasted: List<Any> = s // implicit cast
    

    So why don't you have to cast to List<out Any>? The List interface doesn't have any functions that allow you to add stuff to it. It is defined with an out-projected type in its declaration, so when you type List<Any> it is already the same thing as a List<out Any>

    If you try to do this with a MutableList, which accepts putting items into it and is not defined with an out-projected type, then you will run into the same warning as you did with the array.

    val s = mutableListOf("some string")
    val upcasted: MutableList<Any> = s as MutableList<Any> // warning here and implicit cast impossible
    

    The difference between this and the Array is that there is type-erasure, so you won't have a runtime exception if you try to add some non-String to this list. But there is the possibility of hidden bugs if you are not careful, hence the warning.