Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

loops

map

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 I've inherited removes the given entry, and adds it back in after making some changes to the POJO.

This doesn't work well, since you shouldn't be modifying a map whilst your iterating through it (method is synchronised, but ConcurrentModificationException still appears)

My question is, if I need to iterate over a map and change values, what are the best practices/methods I can use for doing so? To create a separate map and build that up as I go, then return the copy?

like image 924
Jimmy Avatar asked Nov 02 '10 14:11

Jimmy


People also ask

How do you overcome ConcurrentModificationException?

How do you fix Java's ConcurrentModificationException? There are two basic approaches: Do not make any changes to a collection while an Iterator loops through it. If you can't stop the underlying collection from being modified during iteration, create a clone of the target data structure and iterate through the clone.

What is ConcurrentModificationException and how it can be prevented?

The ConcurrentModificationException occurs when an object is tried to be modified concurrently when it is not permissible. This exception usually comes when one is working with Java Collection classes. For Example - It is not permissible for a thread to modify a Collection when some other thread is iterating over it.


5 Answers

Two options:

Option 1

The current code I've inherited removes the given entry, and adds it back in after making some changes to the POJO.

Are you changing the reference to the POJO? E.g., so the entry points to something else entirely? Because if not, there's no need to remove it from the map at all, you can just change it.

Option 2

If you do need to actually change the reference to the POJO (e.g., the value of the entry), you can still do that in place by iterating over the Map.Entry instances from entrySet(). You can use setValue on the entry, which doesn't modify what you're iterating over.

Example:

Map<String,String>                  map;
Map.Entry<String,String>            entry;
Iterator<Map.Entry<String,String>>  it;

// Create the map
map = new HashMap<String,String>();
map.put("one", "uno");
map.put("two", "due");
map.put("three", "tre");

// Iterate through the entries, changing one of them
it = map.entrySet().iterator();
while (it.hasNext())
{
    entry = it.next();
    System.out.println("Visiting " + entry.getKey());
    if (entry.getKey().equals("two"))
    {
        System.out.println("Modifying it");
        entry.setValue("DUE");
    }
}

// Show the result
it = map.entrySet().iterator();
while (it.hasNext())
{
    entry = it.next();
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

The output (in no particular order) is:

Visiting two
Modifying it
Visiting one
Visiting three
two=DUE
one=uno
three=tre

...without any modification exception. You will probably want to synchronize this in case something else is also looking at / mucking with that entry.

like image 92
T.J. Crowder Avatar answered Sep 21 '22 07:09

T.J. Crowder


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.

like image 27
Stephen C Avatar answered Sep 24 '22 07:09

Stephen C


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();
    }
}
like image 22
Sean Patrick Floyd Avatar answered Sep 21 '22 07:09

Sean Patrick Floyd


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().

like image 30
mindas Avatar answered Sep 24 '22 07:09

mindas


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.

like image 22
Neeme Praks Avatar answered Sep 23 '22 07:09

Neeme Praks