It is a quirk of the implementation.
The exception is thrown on a best-effort basis only, there is no guarantee it will be thrown.
To understand why, you need to understand how enhanced for loops are desugared:
for (String i : a) {
a.remove(i);
}
Is compiled to something like:
Iterator<String> it = a.iterator();
while (it.hasNext()) {
String i = a.next();
a.remove(i);
}
Next, the iterator is implemented something like:
int idx = 0;
boolean hasNext() {
return idx < a.size();
}
Object next() {
checkForModification(); // throws exception if list has been modified.
return a.get(idx++);
}
So, if the list has size 2 initially, execution of the loop proceeds thus:
- Check
hasNext()
: idx == 0
, and 0 < 2
is true, so it returns true.
- Call
next()
: retrieve i = a.get(0)
, increment idx
, so idx == 1
.
- Remove
i
from a
. Now a.size() == 1
.
The loop has executed once. Now it executes again:
- Check
hasNext()
: idx == 1
, and 1 < 1
is false, so it returns false.
So execution stops, no ConcurrentModificationException
. The list still contains the initial second item.
If the list is any bigger than 2 initially, the loop executes a second time, (because idx == 1
, and size() == initial size() - 1
, so idx < initial size() - 1
), so next()
will be called again after the remove()
. The list has been modified, so checkForModification()
throws an exception.