2

Why, with enhanced for loops in Java, can I not change what I am iterating through references to? Not the iterator, but the field of the data type I am iterating through.

I want to iterate through a list of Maps(MultiValueMaps to be precise), and check to see if the values of the map can be updated.

My problem is that the actual logic to change this is nested deep within other loops (outline code below) that are based off of the map, and I can't find a way to do the replace without ConcurrentModificationExceptions being thrown. The reason is when I get ConcurrentModificationException taken care of for one loop, it breaks a different one since the code is nested deep.

In the main loop, iterating through the maps, I want to create a new map, that I just add things to, and in the end replace the iterating map with it. But this doesn't work.

Why is this, and what I can I do or research to help me resolve this?

Pseudo Code

for( Map loopMap : listOfMaps ) {
    Map tempMap = new HashMap();

    Set entrySet = loopMap.entrySet();
    Iterator<MultiValueMap.Entry> iter = entrySet.iterator();

    while( iter.hasNext() ) {
        Map.Entry entry = (Entry) iter.next();

        if( entry.getValue() == "test" ) {
            tempMap.put( entry.getKey(), "new value" );
        }
    }
    loopMap = tempMap;
}

Why can I not change what the map refers to even though I am able to change what is refering to's value?

FreakyDan
  • 559
  • 1
  • 7
  • 24
  • It would help if you'd give a short but complete example - you've provided pseudo-code with a lot of stuff that we don't need, whereas I'm sure you could demonstrate the *real* problem in about 10 lines of real code that we could compile... – Jon Skeet Aug 06 '14 at 17:11
  • possible duplicate of [java.util.ConcurrentModificationException](http://stackoverflow.com/questions/8189466/java-util-concurrentmodificationexception) – DavidPostill Aug 06 '14 at 17:12
  • Use a regular for loop instead? – DavidPostill Aug 06 '14 at 17:14
  • @JonSkeet Added actual code, cleaned it up as much as possible to only have code pertaining to issue – FreakyDan Aug 06 '14 at 17:22
  • @DavidPostill I know how the exceptions works, and that posted helped me initially, but the real problem is that I have nested loops that both get edited, and I don't know how to keep the exception from being thrown for both – FreakyDan Aug 06 '14 at 17:23
  • @FreakyDan: I'd be astonished if you really couldn't think of a shorter example, to be honest. (It doesn't need to do anything of any real use - *just* demonstrate the issue.) It's still not a short but *complete* program, either... – Jon Skeet Aug 06 '14 at 17:28

1 Answers1

3

Because you work with a copy of the reference value, not with the direct reference. Note that enhanced for loop will use an Iterator behind the scenes for you. It looks like this:

for (Iterator<YourClass> it = yourCollection.iterator(); it.hasNext(); ) {
    YourClass yourClass = it.next();
    //do whatever...
    //and looks like you change yourClass value here
    //which is a local variable, not the real object reference inside your collection

    //also, you cannot add/remove an element here to the collection being iterated
    //because this will throw a ConcurrentModificationException
}

The solution would be adding all the new elements into a new collection (List, Map, Set or whatever you're using) and, after all your logic, replace your current collection with your new collection. This would be the pseudocode (I cannot provide a more accurate code because you don't provide enough info):

Collection<YourData> newCollection = ... //initialize it
for (... : currentCollection) {
     if (...) {
         YourData yourData = new YourData();
         //fill yourData variable
         //...
         //add it into newCollection
         newCollection.add(yourData);
     }
}
//roughly, you will end doing this or similar
currentCollection = newCollection;

With your new example, the element to be modified will be a Map, so fill the new key-value pairs into a new Map and, in the end, iterate through this map to replace the elements in your current map:

Map<YourKey, YourValue> newMap = new HashMap<>(); //the implementation doesn't really matter for this one
for(Map.EntrySet<YourKey, YourValue> entrySet : currentMap.entrySet()) {
      if (...) {
          YourValue newValue = ...;
          //compute the newValue data
          //...
          newMap.put(entrySet.getKey(), newValue); 
      }
}
for (Map.EntrySet<YourKey, YourValue> entrySet : newMap.entrySet()) {
    currentMap.put(entrySet.getKey(), entrySet.getValue());
}
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • I have thought about creating a temp `List` but with the data I am processing it can get incredibly large, and I don't want to necessarily have a full copy. Is it possible to do on a map by map basis? That way I would only need the memory for one extra map, not all of them. – FreakyDan Aug 06 '14 at 17:26
  • @FreakyDan the only additional memory would be the new map and the new values computed. Note that the other object references are in heap once regardless in how many collections they're stored. – Luiggi Mendoza Aug 06 '14 at 17:30