Java 遍历集合,在循环中删除对象时避免 ConcurrentModificationException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/223918/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:40:17  来源:igfitidea点击:

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

javacollectionsiteration

提问by Claudiu

We all know you can't do the following because of ConcurrentModificationException:

我们都知道你不能做以下事情,因为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 Collectionhere, not necessarily an ArrayList, so you can't rely on get.

我在Collection这里也使用了一个任意的,不一定是一个ArrayList,所以你不能依赖get.

采纳答案by Bill K

Iterator.remove()is safe, you can use it like this:

Iterator.remove()是安全的,你可以这样使用它:

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 waywhile the iteration is in progress.

请注意,这Iterator.remove()是在迭代期间修改集合的唯一安全方法;如果在迭代过程中以任何其他方式修改了基础集合,则行为是未指定的。

Source:docs.oracle > The Collection Interface

来源:docs.oracle > 集合接口



And similarly, if you have a ListIteratorand want to additems, you can use ListIterator#add, for the same reason you can use Iterator#remove — it's designed to allow it.

同样,如果您有 aListIterator并且想要添加项目,您可以使用ListIterator#add,原因与您可以使用的原因相同Iterator#remove - 它旨在允许它。



In your case you tried to remove from a list, but the same restriction applies if trying to putinto a Mapwhile iterating its content.

你的情况,你想从列表中删除,但同样的限制,如果想put成为一个Map在迭代的内容。

回答by Claudiu

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.

我认为由于 foreach 循环是用于迭代的语法糖,因此使用迭代器无济于事……但它为您提供了此.remove()功能。

回答by RodeoClown

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)

您可以像您提到的那样直接使用迭代器,也可以保留第二个集合并将您要删除的每个项目添加到新集合中,然后在最后使用 removeAll。这允许您以增加内存使用和 CPU 时间为代价继续使用 for-each 循环的类型安全(应该不是一个大问题,除非您有非常非常大的列表或非常旧的计算机)

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

回答by Ashish

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.

既然问题已经回答了,即最好的方法是使用迭代器对象的remove方法,我会详细说明"java.util.ConcurrentModificationException"抛出错误的地方。

Every collection class has a private class which implements the Iterator interface and provides methods like next(), remove()and hasNext().

每个集合类都有一个私有类,它实现了 Iterator 接口并提供了诸如next()remove()和 之类的方法hasNext()

The code for next looks something like this...

next 的代码看起来像这样......

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Here the method checkForComodificationis implemented as

这里的方法checkForComodification实现为

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 modCountgetting different from expectedModCount, resulting in the exception ConcurrentModificationException.

因此,如您所见,如果您明确尝试从集合中删除一个元素。它导致modCount越来越不同expectedModCount,导致异常ConcurrentModificationException

回答by Priyank Doshi

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

制作现有列表的副本并迭代新副本。

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

回答by Donald Raab

With Eclipse Collections, the method removeIfdefined on MutableCollectionwill work:

使用Eclipse CollectionsremoveIfMutableCollection 上定义的方法将起作用:

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:

使用 Java 8 Lambda 语法,这可以写成如下:

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 removeIfmethod was added on the java.util.Collectioninterface in Java 8.

Predicates.cast()此处需要调用 ,因为在 Java 8 中removeIfjava.util.Collection接口上添加了默认方法。

Note:I am a committer for Eclipse Collections.

注意:我是Eclipse Collections的提交者。

回答by Antzi

Same answer as Claudiuswith a for loop:

Claudius相同的答案,带有 for 循环:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

回答by ajax333221

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.

我不记得我是从哪里读到的……为了公正,我会制作这个维基,希望有人找到它,或者只是为了不赢得我不配的代表。

回答by Nandhan Thiravia

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.

这将避免并发异常。

回答by assylias

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

在 Java 8 中,您可以使用removeIf方法。应用于您的示例:

Collection<Integer> coll = new ArrayList<>();
//populate

coll.removeIf(i -> i == 5);