natpryce
09/10/2020, 12:22 PMList.of(…)
and List.copyOf(…)
create immutable lists. The Kotlin equivalents, listOf(…)
and anIterable.toList()
actually create mutable lists but return the List interface that doesn’t allow mutation. In Kotlin, that’s not a problem — lists are processed by being transformed with map/flatMap/filter/etc. and so nothing holds on to a reference to mutable lists and aliasing errors do not occur. However… when you pass the Kotlin List across the Java interop boundary, Java sees it as a mutable java.util.List and, because the Kotlin list is actually mutable, Java can mutate a list through an interface that the Kotlin type system considers to be unmodifiable. Worse, the Kotlin code is now more error prone than the Java code, because the Java code would fail immediately if you tried to mutate the immutable list, but the Kotlin code allows that list to be mutated.
Suggestions:
• when the kotlin compiler is targeting JVM version 11 and above, translate Kotlin’s listOf and toList to the JDK List.of and List.copyOf functions.
• on 1.8 and below, wrap Kolin Lists with Collections.unmodifiableList (or some equivalent in the Kotlin runtime) when being passed across the interop boundary.jw
09/10/2020, 12:30 PMnatpryce
09/10/2020, 12:45 PMilya.gorbunov
09/10/2020, 4:01 PMList.of
prohibits nullable values, and `Set.of`/`Map.of` additionally prohibit duplicate elements/keys.ilya.gorbunov
09/10/2020, 4:03 PMwrap Kolin Lists with Collections.unmodifiableList (or some equivalent in the Kotlin runtime) when being passed across the interop boundary.This would change their identity making the interop less transparent.
Nico
09/10/2020, 9:15 PMilya.gorbunov
09/10/2020, 10:23 PMbuildList
, buildSet
, buildMap
. The collections returned by them are frozen, so they cannot be mutated even with downcast and even from java.
We have some thoughts about using these collection implementations for results of collection operations, such as map,filter, etc, but the effect of this change still has to be carefully evaluated.Zac Sweers
10/18/2020, 1:19 AMjw
10/18/2020, 3:27 AMdmcg
10/21/2020, 10:26 AMlistOf(…)
returns a List that can be downcast to MutableList
but which is actually immutable at runtime
val aList: List<String> = listOf("0", "1")
val aMutableList: MutableList<String> = aList as MutableList<String>
aMutableList.removeAt(1) // throws UnsupportedOperationException
dmcg
10/21/2020, 10:33 AMList
cannot be downcast to MutableList
class MyList<T>(vararg items: T): List<T> by items.toList()
val aList: List<String> = MyList("0", "1")
val aMutableList: MutableList<String> = aList as MutableList<String> // throws ClassCastException
dmcg
10/21/2020, 1:10 PMlistOf(...)
is mutable, you just can’t change its size.
val aList: List<String> = listOf("0", "1")
val aMutableList: MutableList<String> = aList as MutableList<String>
aMutableList.set(1, "banana")
assertEquals(listOf("0", "banana"), aMutableList)
jw
10/21/2020, 2:00 PMdmcg
10/21/2020, 2:04 PM