Reputation: 181
I have a crash, that only happened once in my app, due to an UnsupportedOperationException when I try to add an item to a map. For the stacktrace it seems that an AbstractMap is instantiated instead of a MutableMap. The code is in kotlin:
val productMap: MutableMap<ProductModel, Int> =
binding.myView.getProductMap() as? MutableMap<ProductModel, Int>
?: mutableMapOf()
presenter.getProduct()?.let {
productMap.put(it, 0)
}
Could it be that kotlin/java did something weird behind curtains or is there anything I am missing?
The stacktrace:
Fatal Exception: java.lang.UnsupportedOperationException
at java.util.AbstractMap.put(AbstractMap.java:218)
at com.package.MyView.method2(MyView.java:108)
at com.package.MyParentView.method1(MyParentView.java:1278)
Upvotes: 0
Views: 2166
Reputation: 18577
I think the issue here is that MutableMap
is a mapped type: the Kotlin compiler knows about the difference between Map
and MutableMap
, but they both compile down to java.util.Map
in the Java* bytecode. That's because the underlying Java classes don't distinguish mutable from immutable collections; they use the same interface for both, and immutable implementations simply throw UnsupportedOperationException
.
So in this case, the as?
safe cast doesn't do what you want: it's effectively checking only whether the object is a Map
, not whether it's mutable. (I don't know Android, but if getProductMap()
is defined to return a Map
, then that boils down to a simple null check.)
There are several approaches to fixing this, depending on your needs, e.g.:
If you happen to know the concrete, mutable type you expect (such as ArrayList
), then you could cast to that. Since ArrayList
s are always mutable, the put()
should then be safe; but it would do nothing any other type of map was provided.
You could trap the UnsupportedOperationException
in a try
…catch
block. This would avoid the error, but do nothing if an immutable map was provided.
You could create a mutable map from the given map, using toMutableMap()
. This would always update the map — but it might be a private map not used by the view.
(* The stack trace shows that this question is about Kotlin/JVM. You might get different results on other platforms.)
Upvotes: 2
Reputation: 28362
I don't know if this is what happens here, but we should not really cast read-only types to mutable like this. If the return type of getProductMap()
is Map
, not MutableMap
then this is probably for a reason and we should not try to use it as it is mutable.
For example, buildList(): List utility actually returns a list that implements MutableList
, but is in fact read-only and throws exceptions when we try to modify it. Similarly, collections returned from utils like Collections.unmodifiableMap() can be cast to mutable.
I think what you really need to do here is to create a mutable copy of the data in the map:
binding.myView.getProductMap().toMutableMap()
Or, if getProductMap()
returns nullable:
binding.myView.getProductMap()?.toMutableMap() ?: mutableMapOf()
As a side note, your code seems strange to me. It uses the contents of the source map if it is mutable, but it ignores its contents if it is not.
Upvotes: 1