Braj
Braj

Reputation: 46891

How "remove" function works for ArrayList while iterating using for-each loop?

I have a very basic question.

I have created simple ArrayList and I am removing the item while iterating using for-each loop. It gives me java.util.ConcurrentModificationException because I can't remove an item while iterating but when I un-comment the if condition it works fine.

Please can anybody explain me how for-each works in this way.

    ArrayList<String> list1 = new ArrayList<String>();
    list1.add("Hello");
    list1.add("World");
    list1.add("Good Evening");

    for (String s : list1) {
        //if (s.equals("World")) {
            list1.remove(1);
        //}
    }

If I change it to list1.remove(2); or list1.remove(0); then also its working fine.

Note: This is sample code and I know it will work fine using Iterator. My sole purpose of this question is to know how method remove() works perfectly if condition is un-commented no matter what index you are removing from the list.

Upvotes: 3

Views: 3957

Answers (3)

Boann
Boann

Reputation: 50061

The list has a variable called modCount, which means "modification count". Whenever you call remove (or perform other structural modifications), it increments the modCount.

The iterator can't keep track of its position in the list if you are adding or removing elements without telling the iterator. So as a safety check, at the start of iteration, the iterator makes a note of the modCount, saving it as expectedModCount. When each item is read from the iterator, the iterator checks to make sure the modCount still equals the expected value, and throws an exception if it doesn't.

Usually, this will successfully cause the exception to be thrown if the list is unsafely modified during iteration. However, it's not sufficient in this case when the if statement is enabled. After your code has read "World", that item is removed, and so the list now contains ["Hello", Good Evening"]. The iterator is still at position 1 (which now contains "Good Evening") and when it tries to read the next item, it finds it has now reached the end of the list, so it doesn't bother to check the modCount. Hence, no exception.

Note the caveat in the ConcurrentModificationException documentation: "It is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis."

Even if it doesn't happen to throw the exception in this case, the code is still wrong. To remove an element while iterating, you must use the iterator's own remove method:

for (Iterator<String> it = list1.iterator(); it.hasNext();) {
    String s = it.next();
    if (s.equals("World")) {
        it.remove();
    }
}

That way, the iterator knows that the list has changed and can still iterate correctly.

Alternatively, you can iterate from a temporary copy of the list:

for (String s : new ArrayList<>(list1)) {
    if (s.equals("World")) {
        list1.remove(...);
    }
}

Although, in this simple case, you don't even need to do that; you can just write:

list1.remove("World");

Upvotes: 5

Sagar Ahuja
Sagar Ahuja

Reputation: 43

Use an Iterator and call remove():

Iterator<String> iter = list1.iterator();

while (iter.hasNext()) {
    String str = iter.next();

    if (someCondition)
        iter.remove();
}

Upvotes: 0

Josef Vyskovsky
Josef Vyskovsky

Reputation: 51

You can also use an index-based removal. The drawback of this solution is that the list1.size() gets evaluated during every loop iteration. The positive thing is that removing an item from a List by its index is faster.

for (int i = 0; i < list1.size(); /* i incremented in loop body */) {
  if ("World".equals(list1.get(i))) {
    list1.remove(i);
  }
  else {
    i++;
  }
}

Upvotes: 0

Related Questions