Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop

506,849

Solution 1

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.

Solution 2

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.

Solution 3

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);

Solution 4

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.

Solution 5

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);
}
Share:
506,849
Claudiu
Author by

Claudiu

Graduated from Brown University. E-mail: [email protected]

Updated on July 08, 2022

Comments

  • Claudiu
    Claudiu almost 2 years

    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.

  • John
    John over 15 years
    foreach loop is syntactic sugar for iterating. However as you pointed out, you need to call remove on the iterator - which foreach doesn't give you access to. Hence the reason why you can't remove in a foreach loop (even though you are actually using an iterator under the hood)
  • Claudiu
    Claudiu over 15 years
    this is what i normally do, but the explicit iterator is a more elgant solution i feel.
  • RodeoClown
    RodeoClown over 15 years
    Fair enough, as long as you aren't doing anything else with the iterator - having it exposed makes it easier to do things like call .next() twice per loop etc. Not a huge problem, but can cause issues if you are doing anything more complicated than just running through a list to delete entries.
  • matt b
    matt b over 15 years
    @RodeoClown: in the original question, Claudiu is removing from the Collection, not the iterator.
  • RodeoClown
    RodeoClown over 15 years
    Removing from the iterator removes from the underlying collection... but what I was saying in the last comment is that if you are doing anything more complicated than just looking for deletes in the loop (like processing correct data) using the iterator can make some errors easier to make.
  • Eddified
    Eddified over 11 years
    +1 for example code to use iter.remove() in context, which Bill K's answer does not [directly] have.
  • Eugen
    Eugen about 11 years
    What if you want to remove an element other than the element returned in the current iteration?
  • Bill K
    Bill K about 11 years
    You have to use the .remove in the iterator and that is only able to remove the current element, so no :)
  • PMorganCA
    PMorganCA about 11 years
    using Iterator.remove() only works on the current value provided by the iterator. If you have some condition in your loop that requires removal of some other member of the collection, then RodeoClown's solution is the way to go.
  • Antzi
    Antzi almost 11 years
    Making a copy sounds like a waste of resources.
  • kaskelotti
    kaskelotti about 10 years
    The question explicitly states, that the OP is not necessary using ArrayList and thus cannot rely on get(). Otherwise probably a good approach, though.
  • Claudiu
    Claudiu almost 10 years
    This is a good trick. But it wouldn't work on non-indexed collections like sets, and it'd be really slow on say linked lists.
  • Landei
    Landei almost 10 years
    @Claudiu Yes, this is definitely just for ArrayLists or similar collections.
  • Dan
    Dan over 9 years
    Be aware that this is slower compared to using ConcurrentLinkedDeque or CopyOnWriteArrayList (at least in my case)
  • Admin
    Admin almost 9 years
    @Josh It does work fine for me. Just using it now for a collection of WebElement objects.
  • E_Ri
    E_Ri almost 9 years
    Ooooo! I was hoping something in Java 8 or 9 might help. This still seems rather verbose to me, but I still like it.
  • E_Ri
    E_Ri almost 9 years
    Very interesting. Thank you! I often don't call remove() myself, I instead favour clearing the collection after iterating through it. Not to say that's a good pattern, just what I've been doing lately.
  • Anmol Gupta
    Anmol Gupta over 8 years
    Is implementing equals() recommended in this case too?
  • Blake
    Blake about 8 years
    Is it not possible to put the iterator.next() call in the for-loop? If not, can someone explain why?
  • mre
    mre about 8 years
    @Antzi That depends on the size of the list and the density of the objects within. Still a valuable and valid solution.
  • gaoagong
    gaoagong almost 8 years
    It is possible to put another iterator.next() call in the for-loop, but you will also have to add another check using iterator.hasNext() to make sure you even have another element to retrieve.
  • omerhakanbilici
    omerhakanbilici over 7 years
    by the way removeIf uses Iterator and while loop. You can see it at java 8 java.util.Collection.java
  • Didier L
    Didier L about 7 years
    @omerhakanbilici Some implementations like ArrayList override it for performance reasons. The one you are referring to is only the default implementation.
  • Gopal1216
    Gopal1216 almost 7 years
    Can anyone comment how good is this way of deleting an element from a list if my list has a million elements and there are lots of possibilities of running out of java heap space?
  • Bill K
    Bill K almost 7 years
    If it is a linked list you should be great. If it is an array list then you are in trouble
  • StarSweeper
    StarSweeper over 6 years
    I'm using an ArrayList, this worked perfectly, thanks.
  • John
    John about 6 years
    indexes are great. If it's so common why don't you use for(int i = l.size(); i-->0;) {?
  • Gonen I
    Gonen I almost 6 years
    Iterator.remove() is an optional operation, so it is not necessarily safe (or implemented) for all container iterators
  • Radiodef
    Radiodef almost 6 years
    @GonenI It's implemented for all iterators from collections which aren't immutable. List.add is "optional" in that same sense too, but you wouldn't say it's "unsafe" to add to a list.
  • cellepo
    cellepo over 5 years
    Yep, and note that those are all in java.util.concurrent package. Some other similar/common-use-case classes from that package are CopyOnWriteArrayList & CopyOnWriteArraySet [but not limited to those].
  • cellepo
    cellepo over 5 years
    (Clarification ^) OP is using an arbitraryCollection - Collection interface does not include get. (Although FWIW List interface does include 'get').
  • cellepo
    cellepo over 5 years
    I just added a separate, more detailed Answer here also for while-looping a List. But +1 for this Answer because it came first.
  • OneCricketeer
    OneCricketeer over 5 years
    This still requires very careful calculation of indicies to remove, however.
  • OneCricketeer
    OneCricketeer over 5 years
    Also, this is just a more detailed explanation of this answer stackoverflow.com/a/43441822/2308683
  • cellepo
    cellepo over 5 years
    The additional methods in the ListIterator interface (extension of Iterator) are interesting - particularly its previous method.
  • cellepo
    cellepo over 5 years
    Ah, so its really just the enhanced-for-loop that throws the Exception.
  • cellepo
    cellepo over 5 years
    FWIW - same code would still work after modified to increment i++ in the loop guard rather than within loop body.
  • cellepo
    cellepo over 5 years
    Correction ^: That is if the i++ incrementing were not conditional - I see now that's why you do it in the body :)
  • cellepo
    cellepo over 5 years
    Good to know - thanks! That other Answer helped me understand that it's the enhanced-for-loop that would throw ConcurrentModificationException, but not the traditional-for-loop (which the other Answer uses) - not realizing that before is why I was motivated to write this Answer (I erroneously thought then that it was all for-loops that would throw the Exception).
  • cellepo
    cellepo over 5 years
    Actually, I just learned that although those data structure Objects avoid ConcurrentModificationException, using them in an enhanced-for-loop can still cause indexing problems (i.e: still skipping elements, or IndexOutOfBoundsException...)
  • Lii
    Lii over 5 years
    @AnmolGupta: No, equals is not used at all here, so it doesn't have to be implemented. (But of course, if you use equals in your test then it has to be implemented the way you want it.)
  • Tao Zhang
    Tao Zhang over 5 years
    I have been using this method. It takes a bit more resource, but much more flexible and clear.
  • luckydonald
    luckydonald about 5 years
    Note: i isn't a index but instead the object. Maybe calling it obj would be more fitting.
  • A1m
    A1m over 4 years
    This is a good solution when you are not intending to remove objects inside the loop itself, but they are rather "randomly" removed from other threads (e.g. network operations updating data). If you find yourself doing these copies a lot there is even a java implementation doing exactly this: docs.oracle.com/javase/8/docs/api/java/util/concurrent/…
  • JJ Brown
    JJ Brown over 4 years
    As of Java 8, this approach is built-in as l.removeIf(i -> condition(i));, see stackoverflow.com/a/29187813/929708
  • Allix
    Allix almost 4 years
    I'm using this approach and still get a ConcurrentModificationException. Can someone explain to me why this is?
  • Shankha057
    Shankha057 almost 4 years
    Did you take into account the performance hit? Each time you "write" to this structure, it's contents will be copied to a new object. All this is bad for performance.
  • Anupam
    Anupam over 3 years
    This should be the accepted answer now, this will be applicable and desirable for most people looking at the answers here.
  • Quimbo
    Quimbo over 3 years
    @bill-k Why you verify if the string is empty? if (string.isEmpty())
  • Bill K
    Bill K over 3 years
    @Quimbo it is just a condition...the example shown is to remove empty strings. I didn’t write it, so many people have edited this question—look at the history for fun. Personally I liked my original answer, it was like two lines, but I guess it’s important to be as clear as possible with such a popular answer
  • Slion
    Slion over 3 years
    Was already suggested above back in 2012:stackoverflow.com/a/11201224/3969362 Making a copy of the list is what they are typically doing with listeners on Android. It's a valid solution for small lists.
  • Slion
    Slion over 3 years
    Making a copy of the list is what they are typically doing with listeners on Android. It's a valid solution for small lists.
  • marticztn
    marticztn almost 3 years
    Is this still the recommended way in 2021?
  • Bill K
    Bill K almost 3 years
    @marticztn If you need to remove an object (or objects) from a collection inside a loop, then yes, however these days I would personally use the streaming api and filter it as the first step. This leaves the collection untouched (So it doesn't solve the "Remove it" problem, but it does let you use the collection without that data), and you could collect it into a new collection perhaps. This would be way less performant because it would make a copy of the entire collection, however the streaming API has some big benefits when it comes to threading--I guess it's up to you and your use case.
  • RuneMage
    RuneMage over 2 years
    A condensed but more comprehensive answer.
  • user207421
    user207421 about 2 years
    Your first example is not equivalent to your second, or to the OP's code.
  • user207421
    user207421 about 2 years
    It isn't the best way and it isn't recommended. Don't use quote formatting for text that isn't quoted. If it is quoted, provide a citation.
  • user207421
    user207421 about 2 years
    The point here is not the while loop but removing via the Iterator.