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
Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
提问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 Collection
here, 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
And similarly, if you have a ListIterator
and 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 put
into a Map
while 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 checkForComodification
is 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 modCount
getting 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 removeIf
defined on MutableCollectionwill work:
使用Eclipse Collections,removeIf
在MutableCollection 上定义的方法将起作用:
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 removeIf
method was added on the java.util.Collection
interface in Java 8.
Predicates.cast()
此处需要调用 ,因为在 Java 8 中removeIf
的java.util.Collection
接口上添加了默认方法。
Note:I am a committer for Eclipse Collections.
注意:我是Eclipse Collections的提交者。
回答by Antzi
回答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 removeIf
method. Applied to your example:
在 Java 8 中,您可以使用新removeIf
方法。应用于您的示例:
Collection<Integer> coll = new ArrayList<>();
//populate
coll.removeIf(i -> i == 5);