val list = listOf(1, 2, 3)
fun main() {
if (list is MutableList) {
list.add(4)
}
}
The above code throws the below runtime exception.
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add (:-1)
at java.util.AbstractList.add (:-1)
at FileKt.main (File.kt:5)
After reading Kotlin's collection documentation I understand that the kotlin differentiate mutable and immutable collection via interfaces and the immutable interface doesn't have any mutable methods like add()
but I wonder what would be the underlying implementation for both List and MutableList on JVM platform? In this case, it looks like the underlying concrete class has the add()
method but it throws an exception.
Question:
As I don't have much experience in Java it's quite hard for me to find the concrete implementation of the List and MutableList interface in JVM. It would be great if someone point me to the code or way to track what's the actual java collection under these interfaces. Are both these interfaces get implemented by the same java collections or different ones?
Note:
I'm doing this for my learning and I know it's really bad code and I should use.toMutableList()
extension function instead of the smart cast.
CodePudding user response:
As you say, Java doesn't distinguish mutable from immutable lists. (Or at least, not in the type system.) So both the kotlin.List
and kotlin.MutableList
types are mapped to the java.util.List
interface.
The unfortunate consequence of this is that the check:
if (list is MutableList)
…doesn't do what it looks like. It's really just checking whether the object is a java.util.List
, and so both mutable and immutable lists will pass the test.
That's why your code is going on to throw an UnsupportedOperationException
(which is the Java way to distinguish immutable lists, and indeed any other optional features).
I don't know of any way to reliably distinguish mutable from immutable lists, short of trying to modify them and seeing whether they throw that exception…
It's also worth being aware that listOf()
doesn't promise to return any specific implementation. All you can tell is that it'll return something implementing kotlin.List
, but it may be mutable or immutable. (In practice, I think it varies depending whether you pass zero, one, or more items. But the exact implementation may well change in future releases of Kotlin, so you shouldn't make any assumptions.)
Similarly, mutableListOf()
returns some implementation of kotlin.MutableList
, but makes no promises which one.
In practice, if you need a mutable list, then you should create one explicitly (either by creating a specific class such as ArrayList
, or better still, calling mutableListOf()
and letting it pick the most suitable type). Or if you don't have control over the list creation, then — as you say — you can call toMutableList()
(which may well be a no-op if the list is already mutable).
CodePudding user response:
Lists can almost always be cast to MutableLists (unless you wrote your own class that implements Kotlin List and not MutableList) because most (maybe all?) of Kotlin’s functions that generate lists use Java Lists under the hood, and Java Lists are always equivalent to Kotlin MutableList. Java’s way of making a List immutable is to throw an exception at runtime when you try to mutate it, which is why you’re getting a crash.
Basically, you should never cast to a MutableList because it is inherently unsafe. It is a common design pattern for classes to expose read-only views of MutableLists as Lists to protect them from being mutated externally. This is how Kotlin protects you from crashes when working with Lists that must not be mutated. If you subvert this design pattern by casting, sometimes it will succeed at runtime because the underlying list was not a Java immutable list implementation, and then you will trigger bugs that are very difficult to track down.
If you want to explore source code, in IntelliJ IDEA or Android Studio, you can Ctrl click the function to see it’s source code. So in this case you could have done that with listOf
and clicked in through until you got to the List implementation being generated in Java.