Search code examples
javahashmapconcurrenthashmap

Hashmap behaves differently upon remove() and put() while iterating through Hashmap


I was trying to understand hashmap better, I looked into the code and the possibilities. When this came to me

//HashMap
Map<String, String> myMap = new HashMap<>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
System.out.println("HashMap before iterator: " + myMap);

for(String key : myMap.keySet()){
    if(key.equals("3")) {
        myMap.put(key + "new", "new3");
        myMap.remove("2");      
    }
};

for(String key : myMap.keySet()){
    if(key.equals("3")) {
        myMap.put(key + "new", "new3");
        break;  
    }
};
System.out.println("HashMap after iterator: " + myMap);

Output of this is

HashMap before iterator: {1=1, 2=1, 3=1}
HashMap after iterator: {1=1, 3=1, 3new=new3}

The iterator is fail-fast in hashmap, then how is it able to accommodate this changes. Upon this, I came to know that there is a counter which checks the size of the hashmap and throws the exception when the size doesn't match in next().

What I don't get is how breaking the loop avoid the call for next() at the right moment to skip the check. I even looked at the code of the iterator HashIterator I coudnt figure hoe does next() doesnt call for when the code hits break;

And why this code doesn't throw an error.

        //HashMap
    HashMap<String, String> myMap = new HashMap<>();
    myMap.put("1", "1");
    myMap.put("2", "1");
    myMap.put("3", "1");
    System.out.println("HashMap before iterator: "+myMap);

    for(String key : myMap.keySet()){
        if(key.equals("3")) 
            myMap.remove("2");      
    };

    System.out.println("HashMap  after iterator: " + myMap);

    for(String key : myMap.keySet()){
        if(key.equals("3")) 
            myMap.put(key + "new", "new3");
    };

    System.out.println("HashMap  after iterator: " + myMap);

Please help me understand this better.


Solution

  • Well, HashIterator checks the modCount in next(), but doesn't check it in hasNext():

        public final boolean hasNext() {
            return next != null;
        }
    
        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }
    

    Since you are making a structural change in the HashMap in the final iteration of the loop, after that change hasNext() returns false and next() is not called again. Hence no exception.

    Note that the iterator created by the enhanced for loop is not aware of the fact that you added an extra Entry to the Map. Therefore it won't iterate over that extra Entry. The fail-fast mechanism can only work if the iterator's next() method is called, but as explained earlier, that doesn't happen when you make the change in the final iteration of the loop.