I was surprised to see that this program even compiles, but the result surprised me even more:
import java.util.Collections.swap
fun main(args: Array<String>)
{
val immutableList = List(2) { it } // contents are [0, 1]
swap(immutableList, 0, 1)
println(immutableList) // prints [1, 0]
}
The swap
function is implemented in the library as:
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
where the List
is a mutable Java list, not an immutable Kotlin one. So I thought that other Java functions will work as well. For instance:
reverse(immutableList)
works, but others, such as the fill
function, does not even compile:
fill(immutableList, 3)
produces the following error message:
Type inference failed: fun fill(p0: MutableList!, p1: T!): Unit cannot be applied to (List,Int) Type mismatch: inferred type is List but MutableList! was expected
The List
argument of the fill
function however has different type bounds than reverse
:
public static <T> void fill(List<? super T> list, T obj)
public static void reverse(List<?> list)
so it seems that without the type bounds, the Java functions can do whatever they want.
Can someone explain how is this possible? Is this by design, or is it just a limitation of the interop?
functions that doesn't compile has nothing to do with kotlin but how covariant and contravariant collections are handled by java.
from Java Generics and Collections
You cannot put anything into a type declared with an extends wildcard—except for the value null, which belongs to every reference type
for example if you have following code.
List<? extends Number> numbers = new ArrayList<Integer>();
Then you can do this
numbers.add(null);
but if you tried to do any of the following
numbers.set(0, Integer.valueOf(10)); // case 1
numbers.set(1, numbers.get(0)); // case 2
in case 1 compiler will not let you do it because there is no way for the compiler to know the exact type of list, in this case its a list of integers in some other case it may be assigned a list of doubles based on some runtime condition.
in case 2 the compiler is not able to confirm the type of object that is being inserted into the list, and an error is produced. you can solve the problem in case 2 using Wildcard Capture.
Second is your question about mutating kotlin lists in java methods.
we have to understand that kotlin collections classes are same old java collection classes and kotlin doesn't have its own collections implementation. what kotlin does is that it divides the java collection interfaces into mutable and read only. if you create an array list in kotlin and then check its class you will get same java.util.ArrayList
Now given that java doesn't have any notion of mutable list and read only list as kotlin does, there is no way to stop java methods from mutating your read only kotlin list. because for java code that is just a list implementation.
following is the relevant text from book Kotlin in Action
When you need to call a Java method and pass a collection as an argument, you can do so directly without any extra steps. For example, if you have a Java method that takes a java.util.Collection as a parameter, you can pass any Collection or Mutable Collection value as an argument to that parameter.
This has important consequences with regard to mutability of collections. Because Java doesn’t distinguish between read-only and mutable collections, Java code can modify the collection even if it’s declared as a read-only Collection on the Kotlin side. The Kotlin compiler can’t fully analyze what’s being done to the collection in the Java code, and therefore there’s no way for Kotlin to reject a call passing a read-only Collection to Java code that modifies it.
It should be noted that your version of swap
is not compileable. It is the result of decompiling the actual library code that casts the List to a raw type so it can read and write with it despite the wildcard.
The swap
method uses a wildcard type, and so there is no limitation between the interface mapping in the interop. Kotlin maps both List and MutableList to Java's List, so it unfortunately allows misuse like this. I suppose it is necessary to avoid preventing use of Java libraries.
Your fill
method uses a contravariant (consumer) bounded type <? super T>
which is incompatible with Kotlin's List. Kotlin's List is declared with a covariant (producer) type only: List<out T>
. So they are not a match for each other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With