Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing element from list in counted loop vs iterator [duplicate]

Tags:

java

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.
like image 796
AfterWorkGuinness Avatar asked Aug 26 '15 23:08

AfterWorkGuinness


People also ask

Can iterator remove elements during iteration?

An element can be removed from a Collection using the Iterator method remove(). This method removes the current element in the Collection. If the remove() method is not preceded by the next() method, then the exception IllegalStateException is thrown.

How will you efficiently remove elements while iterating a Collection?

The right way to remove objects from ArrayList while iterating over it is by using the Iterator's remove() method. When you use iterator's remove() method, ConcurrentModfiicationException is not thrown.

Can we remove from list while iterating?

If we remove an item from an ArrayList while iterating it, the list. remove(s) will throws java. util. ConcurrentModificationException .

Is it faster to iterate over a list or a set?

Iterating over a List is much much faster than iterating over a set. The currently accepted answer is using a very small set and list and hence, the difference is negligible there.


2 Answers

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.

like image 107
Mifeet Avatar answered Sep 22 '22 04:09

Mifeet


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.

like image 36
Prashant Avatar answered Sep 25 '22 04:09

Prashant