csdnceshi68
local-host
2008-10-21 23:23
采纳率: 20%
浏览 1.2k
已采纳

通过集合进行迭代,避免在循环中修改(移除)时的 ConcurrentModificationException

We all know you can't do this:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc... this apparently works sometimes, but not always. Here's some specific code:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 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.

转载于:https://stackoverflow.com/questions/223918/iterating-through-a-collection-avoiding-concurrentmodificationexception-when-mo

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

21条回答 默认 最新

  • weixin_41568208
    北城已荒凉 2008-10-21 23:27
    已采纳

    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.

    点赞 评论
  • csdnceshi80
    胖鸭 2008-10-21 23:26

    This works:

    Iterator<Integer> iter = l.iterator();
    while (iter.hasNext()) {
        if (iter.next().intValue() == 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.

    点赞 评论
  • weixin_41568208
    北城已荒凉 2008-10-21 23:32

    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<Integer>();
        for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
        }
        for (Integer i : l)
        {
            if (i.intValue() == 5)
                itemsToRemove.add(i);
        }
    
        l.removeAll(itemsToRemove);
        System.out.println(l);
    }
    
    点赞 评论
  • csdnceshi66

    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.

    点赞 评论
  • csdnceshi63
    elliott.david 2012-06-26 05:28

    Make a copy of existing list and iterate over new copy.

    for (String str : new ArrayList<String>(listOfStr))     
    {
        listOfStr.remove(/* object reference or index */);
    }
    
    点赞 评论
  • csdnceshi59
    ℙℕℤℝ 2012-12-18 23:08

    With Eclipse Collections (formerly GS 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.

    点赞 评论
  • weixin_41568127
    ?yb? 2013-08-21 12:39

    Same answer as Claudius with a for loop:

    for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
        Object object = it.next();
        if (test) {
            it.remove();
        }
    }
    
    点赞 评论
  • csdnceshi72
    谁还没个明天 2013-10-12 04:57

    this might not be the best way, but for most of the small cases this should acceptable:

    "create a second empty-array and add only the ones you want to keep"

    I don't remeber where I read this from... for justiness I will make this wiki in hope someone finds it or just to don't earn rep I don't deserve.

    点赞 评论
  • csdnceshi53
    Lotus@ 2013-11-19 09:18

    I have a suggestion for the problem above. No need of secondary list or any extra time. Please find an example which would do the same stuff but in a different way.

    //"list" is ArrayList<Object>
    //"state" is some boolean variable, which when set to true, Object will be removed from the list
    int index = 0;
    while(index < list.size()) {
        Object r = list.get(index);
        if( state ) {
            list.remove(index);
            index = 0;
            continue;
        }
        index += 1;
    }
    

    This would avoid the Concurrency Exception.

    点赞 评论
  • weixin_41568208
    北城已荒凉 2014-05-28 10:11

    With Java 8 you can use the new removeIf method. Applied to your example:

    Collection<Integer> coll = new ArrayList<Integer>();
    //populate
    
    coll.removeIf(i -> i.intValue() == 5);
    
    点赞 评论
  • weixin_41568126
    乱世@小熊 2014-08-29 09:56

    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.

    点赞 评论
  • csdnceshi72
    谁还没个明天 2016-02-04 17:25

    In case ArrayList:remove(int index)- if(index is last element's position) it avoids without System.arraycopy() and takes not time for this.

    arraycopy time increases if(index decreases), by the way elements of list also decreases!

    the best effective remove way is- removing its elements in descending order: while(list.size()>0)list.remove(list.size()-1);//takes O(1) while(list.size()>0)list.remove(0);//takes O(factorial(n))

    //region prepare data
    ArrayList<Integer> ints = new ArrayList<Integer>();
    ArrayList<Integer> toRemove = new ArrayList<Integer>();
    Random rdm = new Random();
    long millis;
    for (int i = 0; i < 100000; i++) {
        Integer integer = rdm.nextInt();
        ints.add(integer);
    }
    ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
    ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
    ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
    //endregion
    
    // region for index
    millis = System.currentTimeMillis();
    for (int i = 0; i < intsForIndex.size(); i++) 
       if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
    System.out.println(System.currentTimeMillis() - millis);
    // endregion
    
    // region for index desc
    millis = System.currentTimeMillis();
    for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
       if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
    System.out.println(System.currentTimeMillis() - millis);
    //endregion
    
    // region iterator
    millis = System.currentTimeMillis();
    for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
        if (iterator.next() % 2 == 0) iterator.remove();
    System.out.println(System.currentTimeMillis() - millis);
    //endregion
    
    • for index loop: 1090 msec
    • for desc index: 519 msec---the best
    • for iterator: 1043 msec
    点赞 评论
  • csdnceshi51
    旧行李 2016-06-23 11:18

    ConcurrentHashMap or ConcurrentLinkedQueue or ConcurrentSkipListMap may be another option, because they will never throw any ConcurrentModificationException, even if you remove or add item.

    点赞 评论
  • csdnceshi54
    hurriedly% 2016-06-30 07:24
    for (Integer i : l)
    {
        if (i.intValue() == 5){
                itemsToRemove.add(i);
                break;
        }
    }
    

    The catch is the after removing the element from the list if you skip the internal iterator.next() call. it still works! Though I dont propose to write code like this it helps to understand the concept behind it :-)

    Cheers!

    点赞 评论
  • weixin_41568134
    MAO-EYE 2017-04-16 20:29

    With a traditional for loop

    ArrayList<String> myArray = new ArrayList<>();
    
       for (int i = 0; i < myArray.size(); ) {
            String text = myArray.get(i);
            if (someCondition(text))
                 myArray.remove(i);
            else 
                 i++;
          }
    
    点赞 评论
  • weixin_41568126
    乱世@小熊 2017-10-13 15:16

    A ListIterator allows you to add or remove items in the list. Suppose you have a list of Car objects:

    List<Car> cars = ArrayList<>();
    // add cars here...
    
    for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
    {
       if (<some-condition>)
       { 
          carIterator().remove()
       }
       else if (<some-other-condition>)
       { 
          carIterator().add(aNewCar);
       }
    }
    
    点赞 评论
  • csdnceshi55
    ~Onlooker 2018-01-15 14:55

    You can iterate the list using for-loop and you need to call list.remove(0). You need to hard-code the index the remove index parameter with zero. See also this answer:

    List<Integer> list = new ArrayList<Integer>();
    
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    int list_size = list.size();
    for (int i = 0; i < list_size; i++) {
        list.remove(0);
    }
    
    点赞 评论
  • csdnceshi70
    笑故挽风 2018-03-17 11:02

    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.

    点赞 评论
  • weixin_41568126
    乱世@小熊 2018-05-03 17:59

    The Best way(Recommended) is use of java.util.Concurrent package . By using this package you can easily avoid this Exception . refer Modified Code

    public static void main(String[] args) {
            Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
    
            for (int i=0; i < 10; ++i) {
                l.add(new Integer(4));
                l.add(new Integer(5));
                l.add(new Integer(6));
            }
    
            for (Integer i : l) {
                if (i.intValue() == 5) {
                    l.remove(i);
                }
            }
    
            System.out.println(l);
        }
    
    点赞 评论
  • csdnceshi78
    程序go 2018-09-17 09:03

    Example of thread safe collection modification:

    public class Example {
        private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
    
        public void removeFromQueue() {
            synchronized (queue) {
                Iterator<String> iterator = queue.iterator();
                String string = iterator.next();
                if (string.isEmpty()) {
                    iterator.remove();
                }
            }
        }
    }
    
    点赞 评论
  • csdnceshi72
    谁还没个明天 2018-09-26 20:15

    I know this question is too old to be about Java 8, but for those using Java 8 you can easily use removeIf():

    Collection<Integer> l = new ArrayList<Integer>();
    
    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }
    
    l.removeIf(i -> i.intValue() == 5);
    
    点赞 评论

相关推荐