5

Why is this legal:

for(int i=0; i < arr.size(); i++) {
    arr.remove(i);
}

But using an iterator or the syntactic sugar of a for each results in a ConcurrentModificationException:

for(String myString : arr) {
   arr.remove(myString);
}
  • Before everyone starts jumping on the bandwagon telling me to use iterator.remove(); I'm asking why the different behavior, not how to avoid the conc mod exception. Thanks.
0

3 Answers 3

4

Let's take a look at how, e.g., ArrayLists's iterator is implemented:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such

    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size) throw new NoSuchElementException();
        // ...
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        // ...
        ArrayList.this.remove(lastRet);
        // ...
        cursor = lastRet;
        lastRet = -1;
    }

Let's look at an example:

List list = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it = list.iterator();
Integer item = it.next();

We remove the first element

list.remove(0);

If we want to call it.remove() now, the iterator would remove number 2 because that's what field lastRet points to now.

if (item == 1) {
   it.remove(); // list contains 3, 4
}

This would be incorrect behavior! The contract of the iterator states that remove() deletes the last element returned by next() but it couldn't hold its contract in the presence of concurrent modifications. Therefore it chooses to be on the safe side and throw an exception.

The situation may be even more complex for other collections. If you modify a HashMap, it may grow or shrink as needed. At that time, elements would fall to different buckets and an iterator keeping pointer to a bucket before rehashing would be completely lost.

Notice that iterator.remove() doesn't throw an exception by itself because it is able to update both the internal state of itself and the collection. Calling remove() on two iterators of the same instance collection would throw, however, because it would leave one of the iterators in an inconsistent state.

Sign up to request clarification or add additional context in comments.

Comments

2

Looking at your code, I am assuming arr is a List. In the top loop you operate on the list directly, and "re-calibrate" your condition at the top when you check

i < arr.size()

So if you remove an element, i has to compare to a lesser value. On the other hand, in the second case you operate on the collection after an iterator has been instantiated, and don't really re-calibrate yourself.

Hope this helps.

Comments

1

In the first one you are modifying an array that it's not being used as an iterator on your for loop.

In the second one you are trying to access to an array that it's being modified at the same time you are iterating with it on the loop. It's why it throws ConcurrentModificationException.

2 Comments

To the downvoter: please don't just downvote the answer, please explain what do you see that it's wrong. I will really appreciate it.
Really, to the downvoters, please tell to me what it's wrong or what do you think I should change. If I'm wrong, please correct me. I'm so intrigued.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.