Reputation: 46891
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
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
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
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