简体   繁体   中英

UnsupportedOperationException when adding to a map in kotlin

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)

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.

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, eg:

  • 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 trycatch 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.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM