Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
We all know you can’t do the following because of ConcurrentModificationException
:
for (Object i : l) { if (condition(i)) { l.remove(i); } }
But this apparently works sometimes, but not always. Here’s some specific code:
public static void main(String[] args) { Collection<Integer> l = new ArrayList<>(); for (int i = 0; i < 10; ++i) { l.add(4); l.add(5); l.add(6); } for (int i : l) { if (i == 5) { l.remove(i); } } System.out.println(l); }
This, of course, results in:
Exception in thread "main" java.util.ConcurrentModificationException
Even though multiple threads aren’t doing it. Anyway.
What’s the best solution to this problem? How can I remove an item from the collection in a loop without throwing this exception?
I’m also using an arbitrary Collection
here, not necessarily an ArrayList
, so you can’t rely on get
.
Iterator.remove()
is safe, you can use it like this:
List<String> list = new ArrayList<>(); // This is a clever way to create the iterator and call iterator.hasNext() like // you would do in a while-loop. It would be the same as doing: // Iterator<String> iterator = list.iterator(); // while (iterator.hasNext()) { for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) { String string = iterator.next(); if (string.isEmpty()) { // Remove the current element from the iterator and the list. iterator.remove(); } }
Note that Iterator.remove()
is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.
Source: docs.oracle > The Collection Interface
And similarly, if you have a ListIterator
and want to add items, you can use ListIterator#add
, for the same reason you can use Iterator#remove
— it’s designed to allow it.
In your case you tried to remove from a list, but the same restriction applies if trying to put
into a Map
while iterating its content.
This works:
Iterator<Integer> iter = l.iterator(); while (iter.hasNext()) { if (iter.next() == 5) { iter.remove(); } }
I assumed that since a foreach loop is syntactic sugar for iterating, using an iterator wouldn’t help… but it gives you this .remove()
functionality.
With Java 8 you can use the new removeIf
method. Applied to your example:
Collection<Integer> coll = new ArrayList<>(); //populate coll.removeIf(i -> i == 5);
Since the question has been already answered i.e. the best way is to use the remove method of the iterator object, I would go into the specifics of the place where the error "java.util.ConcurrentModificationException"
is thrown.
Every collection class has a private class which implements the Iterator interface and provides methods like next()
, remove()
and hasNext()
.
The code for next looks something like this…
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
Here the method checkForComodification
is implemented as
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
So, as you can see, if you explicitly try to remove an element from the collection. It results in modCount
getting different from expectedModCount
, resulting in the exception ConcurrentModificationException
.
You can either use the iterator directly like you mentioned, or else keep a second collection and add each item you want to remove to the new collection, then removeAll at the end. This allows you to keep using the type-safety of the for-each loop at the cost of increased memory use and cpu time (shouldn’t be a huge problem unless you have really, really big lists or a really old computer)
public static void main(String[] args) { Collection<Integer> l = new ArrayList<Integer>(); Collection<Integer> itemsToRemove = new ArrayList<>(); for (int i=0; i < 10; i++) { l.add(Integer.of(4)); l.add(Integer.of(5)); l.add(Integer.of(6)); } for (Integer i : l) { if (i.intValue() == 5) { itemsToRemove.add(i); } } l.removeAll(itemsToRemove); System.out.println(l); }
In such cases a common trick is (was?) to go backwards:
for(int i = l.size() - 1; i >= 0; i --) { if (l.get(i) == 5) { l.remove(i); } }
That said, I’m more than happy that you have better ways in Java 8, e.g. removeIf
or filter
on streams.
Same answer as Claudius with a for loop:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) { Object object = it.next(); if (test) { it.remove(); } }
With Eclipse Collections, the method removeIf
defined on MutableCollection will work:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5); list.removeIf(Predicates.lessThan(3)); Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
With Java 8 Lambda syntax this can be written as follows:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5); list.removeIf(Predicates.cast(integer -> integer < 3)); Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
The call to Predicates.cast()
is necessary here because a default removeIf
method was added on the java.util.Collection
interface in Java 8.
Note: I am a committer for Eclipse Collections.
Make a copy of existing list and iterate over new copy.
for (String str : new ArrayList<String>(listOfStr)) { listOfStr.remove(/* object reference or index */); }
People are asserting one can’t remove from a Collection being iterated by a foreach loop. I just wanted to point out that is technically incorrect and describe exactly (I know the OP’s question is so advanced as to obviate knowing this) the code behind that assumption:
for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes if (obj.isTouched()) { untouchedSet.remove(obj); touchedSt.add(obj); break; // this is key to avoiding returning to the foreach } }
It isn’t that you can’t remove from the iterated Colletion
rather that you can’t then continue iteration once you do. Hence the break
in the code above.
Apologies if this answer is a somewhat specialist use-case and more suited to the original thread I arrived here from, that one is marked as a duplicate (despite this thread appearing more nuanced) of this and locked.