Why doesn't this code throw a ConcurrentModificationException?

As a general rule, ConcurrentModificationExceptions are thrown when the modification is detected, not caused. If you never access the iterator after the modification, it won't throw an exception. This minute detail makes ConcurrentModificationExceptions rather unreliable for detecting misuse of data structures, unfortunately, as they only are thrown after the damage has been done.

This scenario doesn't throw a ConcurrentModificationException because next() doesn't get called on the created iterator after the modification.

For-each loops are really iterators, so your code actually looks like this:

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter = strings.iterator();
while(iter.hasNext()){
    String string = iter.next();
    if ("B".equals(string))
        strings.remove("B");
}
System.out.println(strings);

Consider your code running on the list you provided. The iterations look like:

  1. hasNext() returns true, enter loop, -> iter moves to index 0, string = "A", not removed
  2. hasNext() returns true, continue loop -> iter moves to index 1, string = "B", removed. strings now has length 2.
  3. hasNext() returns false (iter is currently at the last index, no more indices to go), exit loop.

Thus, as ConcurrentModificationExceptions are thrown when a call to next() detects a that a modification has been made, this scenario narrowly avoids such an exception.

For your other two results, we do get exceptions. For "A", "B", "C", "D", after removing "B" we are still in the loop, and next() detects the ConcurrentModificationException, whereas for "A", "B" I'd imagine it's some kind of ArrayIndexOutOfBounds that's being caught and re-thrown as a ConcurrentModificationException


hasNext in the ArrayList's iterator is just

public boolean hasNext() {
    return cursor != size;
}

After the remove call, the iterator is at index 2, and the list's size is 2, so it reports that the iteration is complete. No concurrent modification check. With ("A", "B", "C", "D) or ("A", "B"), the iterator is not at the new end of the list, so next is called, and that throws the exception.

ConcurrentModificationExceptions are only a debugging aid. You cannot rely on them.

Tags:

Java