How to avoid ConcurrentModificationException when iterating over a map and changing values?

前端 未结 7 1008
眼角桃花
眼角桃花 2020-12-06 09:46

I\'ve got a map containing some keys (Strings) and values (POJOs)

I want to iterate through this map and alter some of the data in the POJO.

The current code

相关标签:
7条回答
  • 2020-12-06 10:19

    Iterating over a Map and adding entries at the same time will result in a ConcurrentModificationException for most Map classes. And for the Map classes that don't (e.g. ConcurrentHashMap) there is no guarantee that an iteration will visit all entries.

    Depending on exactly what it is you are doing, you may be able to do the following while iterating:

    • use the Iterator.remove() method to remove the current entry, or
    • use the Map.Entry.setValue() method to modify the current entry's value.

    For other types of change, you may need to:

    • create a new Map from the entries in the current Map, or
    • build a separate data structure containing changes to be made, then applied to the Map.

    And finally, the Google Collections and Apache Commons Collections libraries have utility classes for "transforming" maps.

    0 讨论(0)
  • 2020-12-06 10:19

    Try using ConcurrentHashMap.

    From JavaDoc,

    A hash table supporting full concurrency of retrievals and adjustable expected concurrency for updates.

    For ConcurrentModificationException to occur, generally:

    it is not generally permissible for one thread to modify a Collection while another thread is iterating over it.

    0 讨论(0)
  • 2020-12-06 10:20

    Create a new map (mapNew). Then iterate over the existing map (mapOld), and add all changed and transformed entries into mapNew. After the iteration is complete, put all values from mapNew to mapOld. This might not be good enough if the amount of data is large though.

    Or just use Google collections - they have Maps.transformValues() and Maps.transformEntries().

    0 讨论(0)
  • 2020-12-06 10:22

    For such purposes you should use the collection views a map exposes:

    • keySet() lets you iterate over keys. That won't help you, as keys are usually immutable.
    • values() is what you need if you just want to access the map values. If they are mutable objects, you can change directly, no need to put them back into the map.
    • entrySet() the most powerful version, lets you change an entry's value directly.

    Example: convert the values of all keys that contain an upperscore to uppercase

    for(Map.Entry<String, String> entry:map.entrySet()){
        if(entry.getKey().contains("_"))
            entry.setValue(entry.getValue().toUpperCase());
    }
    

    Actually, if you just want to edit the value objects, do it using the values collection. I assume your map is of type <String, Object>:

    for(Object o: map.values()){
        if(o instanceof MyBean){
            ((Mybean)o).doStuff();
        }
    }
    
    0 讨论(0)
  • 2020-12-06 10:24

    In order to provide a proper answer, you should explain a bit more, what you are trying to achieve.

    Still, some (possibly useful) advice:

    • make your POJOs thread-safe and do data updates on POJOs directly. Then you do not need to manipulate the map.
    • use ConcurrentHashMap
    • keep on using simple HashMap, but build a new map on each modification and switch maps behind the scenes (synchronizing the switch operation or using AtomicReference)

    Which approach is best depends heavily on your application, it is difficult to give you any "best practice". As always, make your own benchmark with realistic data.

    0 讨论(0)
  • 2020-12-06 10:25

    Another approach, somewhat tortured, is to use java.util.concurrent.atomic.AtomicReference as your map's value type. In your case, that would mean declaring your map of type

    Map<String, AtomicReference<POJO>>
    

    You certainly don't need the atomic nature of the reference, but it's a cheap way to make the value slots rebindable without having to replace the entire Map.Entry via Map#put().

    Still, having read some of the other responses here, I too recommend use of Map.Entry#setValue(), which I had never needed nor noticed until today.

    0 讨论(0)
提交回复
热议问题