迭代 Collection,避免在循环中删除对象时出现 ConcurrentModificationException
我们都知道,由于 ConcurrentModificationException
,您无法执行以下操作:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
但这显然有时有效,但并非总是有效。 这是一些特定的代码:
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);
}
当然,这会导致:
Exception in thread "main" java.util.ConcurrentModificationException
即使多个线程没有执行此操作。 反正。
这个问题的最佳解决方案是什么? 如何在循环中从集合中删除项目而不引发此异常?
我在这里还使用了任意 Collection
,不一定是 ArrayList
,因此您不能依赖 get
。
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
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
Iterator.remove()< /code>
是安全的,您可以像这样使用它:
请注意
Iterator.remove()
是在迭代期间修改集合的唯一安全方法; 如果在迭代过程中以任何其他方式修改基础集合,则行为未指定。来源: docs.oracle > 集合接口
同样,如果您有一个
ListIterator
并且想要添加项,您可以使用ListIterator#add
,出于同样的原因,您可以使用Iterator#remove
——它的设计就是允许这样做。在您的情况下,您尝试从列表中删除,但如果在迭代其内容时尝试将
put
放入Map
中,则适用相同的限制。Iterator.remove()
is safe, you can use it like this: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 useListIterator#add
, for the same reason you can useIterator#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 aMap
while iterating its content.这是可行的:
我假设由于 foreach 循环是用于迭代的语法糖,因此使用迭代器不会有帮助......但它为您提供了这个
.remove()
功能。This works:
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.对于 Java 8,您可以使用 新的
removeIf
方法。 应用于您的示例:一个简单的测试作为示例:
当您比较两个列表并希望从两个列表中删除时,它甚至可以工作。
但是,如果其中一个列表有重复项,请确保它在内部循环中迭代,因为对于内部列表,它将删除所有符合条件的元素,但对于外部列表list,当任何元素被删除时,它会立即返回并停止检查。
该测试将失败:
With Java 8 you can use the new
removeIf
method. Applied to your example:A simple test as example:
It even works when you compare two lists and want to remove from both.
However, if one of the list has duplicates, make sure it's iterated in the inner loop, because for inner list, it will remove all elements meeting the criteria, but for outer list, when any element is removed, it will return immediately and stops checking.
This test will fail:
由于问题已经得到解答,即最好的方法是使用迭代器对象的remove方法,我将详细介绍抛出错误
“java.util.ConcurrentModificationException”
的地方。每个集合类都有一个私有类,它实现 Iterator 接口并提供
next()
、remove()
和hasNext()
等方法。接下来的代码看起来像这样...
这里方法
checkForCommodification
实现为因此,如您所见,如果您显式尝试从集合中删除元素。 它会导致
modCount
与expectedModCount
不同,从而导致异常ConcurrentModificationException
。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()
andhasNext()
.The code for next looks something like this...
Here the method
checkForComodification
is implemented asSo, as you can see, if you explicitly try to remove an element from the collection. It results in
modCount
getting different fromexpectedModCount
, resulting in the exceptionConcurrentModificationException
.您可以像您提到的那样直接使用迭代器,或者保留第二个集合并将要删除的每个项目添加到新集合中,然后在最后删除 All。 这允许您继续使用 for-each 循环的类型安全性,但代价是增加内存使用和 CPU 时间(不应该是一个大问题,除非您有非常非常大的列表或非常旧的计算机)
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)
在这种情况下,一个常见的技巧是(曾经是?)向后退:
也就是说,我非常高兴您在 Java 8 中有更好的方法,例如
removeIf
或filter
在流上。In such cases a common trick is (was?) to go backwards:
That said, I'm more than happy that you have better ways in Java 8, e.g.
removeIf
orfilter
on streams.与带有 for 循环的 Claudius 的答案相同:
Same answer as Claudius with a for loop:
复制现有列表并迭代新副本。
Make a copy of existing list and iterate over new copy.
对于 Eclipse Collections,在 MutableCollection 将起作用:
使用 Java 8 Lambda 语法,可以编写如下:
此处需要调用
Predicates.cast()
,因为默认的removeIf 方法已添加到 Java 8 中的
java.util.Collection
接口上。注意: 我是 Eclipse 集合。
With Eclipse Collections, the method
removeIf
defined on MutableCollection will work:With Java 8 Lambda syntax this can be written as follows:
The call to
Predicates.cast()
is necessary here because a defaultremoveIf
method was added on thejava.util.Collection
interface in Java 8.Note: I am a committer for Eclipse Collections.
人们声称不能从 foreach 循环迭代的集合中删除。 我只是想指出,这在技术上是不正确的,并准确地描述了该假设背后的代码(我知道OP的问题非常先进,以至于无法了解这一点):
这并不是说你不能从迭代的
Collection
中删除,而不是一旦删除就无法继续迭代。 因此上面的代码中出现了break
。如果这个答案是一个有点专业的用例并且更适合原始 线程,我们深表歉意 我从这里到达这里,该线程被标记为该线程的重复项(尽管该线程看起来更加细致)并被锁定。
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:
It isn't that you can't remove from the iterated
Colletion
rather that you can't then continue iteration once you do. Hence thebreak
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.
使用传统的 for 循环
With a traditional for loop
ConcurrentHashMap 或 ConcurrentLinkedQueue 或 ConcurrentSkipListMap 可能是另一种选择,因为它们永远不会抛出任何 ConcurrentModificationException,即使您删除或添加项目。
ConcurrentHashMap or ConcurrentLinkedQueue or ConcurrentSkipListMap may be another option, because they will never throw any ConcurrentModificationException, even if you remove or add item.
另一种方法是使用 arrayList 的副本仅用于迭代:
Another way is to use a copy of your arrayList just for iteration:
ListIterator
允许您添加或删除列表中的项目。 假设您有一个Car
对象列表:A
ListIterator
allows you to add or remove items in the list. Suppose you have a list ofCar
objects:现在,您可以使用以下代码删除
Now, You can remove with the following code
我知道这个问题对于 Java 8 来说太旧了,但是对于那些使用 Java 8 的人来说,你可以轻松地使用removeIf():
I know this question is too old to be about Java 8, but for those using Java 8 you can easily use removeIf():
Java并发修改异常
解决方案:迭代器
remove()
方法同步
[关于]Java Concurrent Modification Exception
Solution: iterator
remove()
methodsynchronize
[About]对于上面的问题我有一个建议。 不需要辅助列表或任何额外的时间。 请找到一个可以以不同方式执行相同操作的示例。
这将避免并发异常。
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.
This would avoid the Concurrency Exception.
如果跳过内部 iterator.next() 调用,则问题是从列表中删除元素后。 它仍然有效! 虽然我不建议编写这样的代码,但它有助于理解其背后的概念:-)
干杯!
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!
线程安全集合修改示例:
Example of thread safe collection modification:
我知道这个问题仅假设一个
Collection
,而不是更具体的任何List
。 但是对于那些阅读这个问题并且确实使用List
引用的人来说,您可以使用while
循环来避免ConcurrentModificationException
(while在其中进行修改)而不是如果您想避免Iterator
(或者如果您想一般性地避免它,或者专门避免它以实现与开始到结束停止不同的循环顺序)在每个元素处[我相信这是Iterator
本身可以执行的唯一顺序]):*更新:请参阅下面的注释,澄清类似的操作也可以通过传统-for-loop。
该代码中没有 ConcurrentModificationException。
我们看到循环不是从开头开始,也不是在每个元素处停止(我相信
Iterator
本身做不到)。FWIW 我们还看到
get
在list
上被调用,如果它的引用只是Collection
(而不是更具体的>List
-Collection
类型) -List
接口包含get
,但Collection
接口不包含get
。 如果不是因为这种差异,那么list
引用可能会是Collection
[因此从技术上讲,这个答案将是直接答案,而不是切线答案]。FWIWW相同的代码在修改为在每个元素处开始和停止后仍然有效(就像
Iterator
顺序):I know this question assumes just a
Collection
, and not more specifically anyList
. But for those reading this question who are indeed working with aList
reference, you can avoidConcurrentModificationException
with awhile
-loop (while modifying within it) instead if you want to avoidIterator
(either if you want to avoid it in general, or avoid it specifically to achieve a looping order different from start-to-end stopping at each element [which I believe is the only orderIterator
itself can do]):*Update: See comments below that clarify the analogous is also achievable with the traditional-for-loop.
No ConcurrentModificationException from that code.
There we see looping not start at the beginning, and not stop at every element (which I believe
Iterator
itself can't do).FWIW we also see
get
being called onlist
, which could not be done if its reference was justCollection
(instead of the more specificList
-type ofCollection
) -List
interface includesget
, butCollection
interface does not. If not for that difference, then thelist
reference could instead be aCollection
[and therefore technically this Answer would then be a direct Answer, instead of a tangential Answer].FWIWW same code still works after modified to start at beginning at stop at every element (just like
Iterator
order):一种解决方案可能是旋转列表并删除第一个元素以避免 ConcurrentModificationException 或 IndexOutOfBoundsException
One solution could be to rotate the list and remove the first element to avoid the ConcurrentModificationException or IndexOutOfBoundsException
试试这个(删除列表中所有等于
i
的元素):Try this one (removes all elements in the list that equal
i
):您可以使用 while 循环。
You can use a while loop.
在使用
stream().map()
方法迭代列表时,我最终得到了这个ConcurrentModificationException
。 然而,for(:)
在迭代和修改列表时并没有抛出异常。这是代码片段,如果对任何人有帮助的话:
在这里,我迭代
ArrayList
,并使用 list.remove(obj) 修改它I ended up with this
ConcurrentModificationException
, while iterating the list usingstream().map()
method. However thefor(:)
did not throw the exception while iterating and modifying the the list.Here is code snippet , if its of help to anyone:
here I'm iterating on a
ArrayList<BuildEntity>
, and modifying it using the list.remove(obj)如果使用 HashMap,在较新版本的 Java (8+) 中,您可以选择 3 个选项中的每一个:
If using HashMap, in newer versions of Java (8+) you can select each of 3 options:
最好的方法(推荐)是使用 java.util.concurrent 包。 经过
使用这个包你可以很容易地避免这个异常。 参考
修改后的代码:
The best way (recommended) is use of
java.util.concurrent
package. Byusing this package you can easily avoid this exception. Refer
Modified Code:
当另一个线程也修改集合时,迭代器并不总是有帮助。 我尝试了很多方法,但后来意识到手动遍历集合要安全得多(向后删除):
Iterators are not always helpful when another thread also modifies the collection. I had tried many ways but then realized traversing the collection manually is much safer (backward for removal):
如果 ArrayList:remove(int index)- if(index 是最后一个元素的位置),它可以在没有
System.arraycopy()
的情况下避免,并且不需要时间。如果(索引减少),数组复制时间会增加,顺便说一句,列表的元素也会减少!
最好有效的删除方法是 - 按降序删除其元素:
while(list.size()>0)list.remove(list.size()-1);
//需要 O(1)while(list.size()>0)list.remove(0);
//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))还可以使用递归
java中的递归是一个方法不断调用自身的过程。 java中调用自身的方法称为递归方法。
you can also use Recursion
Recursion in java is a process in which a method calls itself continuously. A method in java that calls itself is called recursive method.