ghostrider
ghostrider

Reputation: 2238

No ConcurrentModificationException while looping using foreach

I am trying to run the below code:

List<String> l = new ArrayList<String>();
l.add("jim");
l.add("pam");

for(String s : l) {
    l.remove(s);
}

But I am not getting any exception. Isn't the for each loop uses Iterator and we cannot change the structure of the List while iterating?

EDIT:

I am getting the ConcurrentModificationExceptionwhen the list has 3+ elements.Why is that the case?

Upvotes: 1

Views: 123

Answers (1)

Slaw
Slaw

Reputation: 45786

The reason you don't get a ConcurrentModificationException is because your list only has two elements which prevents the implementation from detecting the concurrent modification. Here's the Java 13 Iterator implementation used by ArrayList:

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
    int expectedModCount = modCount;

    // prevent creating a synthetic constructor
    Itr() {}

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i < size) {
            final Object[] es = elementData;
            if (i >= es.length)
                throw new ConcurrentModificationException();
            for (; i < size && modCount == expectedModCount; i++)
                action.accept(elementAt(es, i));
            // update once at end to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

As you can see, it checks for concurrent modification in the next() method, but not in the hasNext() method. This is important. If you aren't aware, enhanced for loops use Iterators behind the scenes. In other words, your for-each loop is equivalent to:

for (Iterator<String> itr = l.itr(); itr.hasNext(); ) {
    String e = itr.next();
    l.remove(e);
}

Once your loop removes the first element there's only one element left in the list. However, that element has moved down an index (1 → 0) but the cursor of the iterator has been incremented by one. This makes the cursor equal to the size and thus the loop does not execute a second time. Since the loop doesn't execute a second time the next() method isn't called again and the check for concurrent modification doesn't occur. This also has the side-effect of leaving the second element in the list.

Once your list has three or more elements the loop does execute a second time which allows it to detect the concurrent modification.

Upvotes: 3

Related Questions