Question:
Let's say I have some ArrayList
.
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++)
list.add((int) (Math.random() * 20));
And I want to remove all numbers greater than 10 from it.
You can do this "correctly" through an iterator, getting a guaranteed result.
for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext(); )
if (iterator.next() > 10)
iterator.remove();
But to my surprise, the option also works correctly:
for (Integer i : list)
if (i > 10)
list.remove(i);
Even though it says everywhere that any attempt to remove from a collection in a loop without using an iterator results in a ConcurrentModificationException
.
And, indeed, if we delete unconditionally, we will get just ConcurrentModificationException
for (Integer i : list)
list.remove(i);
Actually, can someone explain the magic or give a tip where you can read on this topic?
Answer:
You seem to be incredibly lucky:
- you didn't have elements whose value was greater than 10
- you had an element whose value was greater than 10, but it was only one and was located in the penultimate place in the list
Let's work through a shorter example.
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(22);
list.add(3);
We have added three numbers. So how does foreach
work
- It gets an iterator.
-
Checks for the presence of the next element
hasNext()
.public boolean hasNext() { return cursor != size(); // cursor is zero initially. }
-
If it returns
true
, then take the next element withnext()
.public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } final void checkForComodification() { // Initially modCount = expectedModCount (our case 5) if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
Then steps 2 and 3 are repeated until hasNext()
returns false
.
If you remove an element from the list, then its size will decrease and modCount
will increase.
If an element is removed during iteration, a ConcurrentModificationException
will be thrown at the line modCount != expectedModCount
.
But what happens if the penultimate element is removed?
> cursor = 0 size = 3 --> hasNext() успешно и next() тоже без эксепшена
> cursor = 1 size = 3 --> hasNext() успешно и next() тоже без эксепшена
When we remove the value 22, the size will decrease to 2.
> cursor = 2 size = 2 --> hasNext() не успешно и next() пропускается.
Otherwise, a ConcurrentModificationException
will be thrown due to modCount != expectedModCount
.
And in this single case, the test will pass with a bang ..
Here is the magic …. Or a bug ….