迭代 Collection,避免在循环中删除对象时出现 ConcurrentModificationException

发布于 2024-07-06 22:08:06 字数 881 浏览 6 评论 0原文

我们都知道,由于 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

旧城烟雨 2024-07-13 22:08:06

Iterator.remove()< /code>是安全的,您可以像这样使用它:

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

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

来源: docs.oracle > 集合接口


同样,如果您有一个ListIterator并且想要添加项,您可以使用ListIterator#add,出于同样的原因,您可以使用Iterator#remove ——它的设计就是允许这样做。


在您的情况下,您尝试从列表中删除,但如果在迭代其内容时尝试将 put 放入 Map 中,则适用相同的限制。

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.

々眼睛长脚气 2024-07-13 22:08:06

这是可行的:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next() == 5) {
        iter.remove();
    }
}

我假设由于 foreach 循环是用于迭代的语法糖,因此使用迭代器不会有帮助......但它为您提供了这个 .remove() 功能。

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.

数理化全能战士 2024-07-13 22:08:06

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

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

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

一个简单的测试作为示例:

    @Test
    public void testRemoveIfOneList() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("two");
        outer.add("three");

        outer.removeIf(o -> o.length() == 3);

        assertEquals(1, outer.size());
    }

当您比较两个列表并希望从两个列表中删除时,它甚至可以工作。

    @Test
    public void testRemoveIfTwoLists() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("two");
        outer.add("three");
        List<String> inner = new ArrayList<>();
        inner.addAll(outer);

        // first, it removes from inner, and if anything is removed, then removeIf() returns true,
        // leading to removing from outer
        outer.removeIf(o -> inner.removeIf(i -> i.equals(o)));

        assertEquals(0, outer.size());
        assertEquals(0, inner.size());
    }

但是,如果其中一个列表有重复项,请确保它在内部循环中迭代,因为对于内部列表,它将删除所有符合条件的元素,但对于外部列表list,当任何元素被删除时,它会立即返回并停止检查。

该测试将失败:

    @Test
    public void testRemoveIfTwoListsInnerHasDuplicates() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("one");
        outer.add("two");
        outer.add("two");
        outer.add("three");
        outer.add("three");
        List<String> inner = new ArrayList<>();
        inner.addAll(outer); // both have duplicates

        // remove all elements from inner(executed twice), then remove from outer
        // but only once! if anything is removed, it will return immediately!!
        outer.removeIf(o -> inner.removeIf(i -> i.equals(o)));

        assertEquals(0, inner.size()); // pass, inner all removed
        assertEquals(0, outer.size()); // will fail, outer has size = 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);

A simple test as example:

    @Test
    public void testRemoveIfOneList() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("two");
        outer.add("three");

        outer.removeIf(o -> o.length() == 3);

        assertEquals(1, outer.size());
    }

It even works when you compare two lists and want to remove from both.

    @Test
    public void testRemoveIfTwoLists() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("two");
        outer.add("three");
        List<String> inner = new ArrayList<>();
        inner.addAll(outer);

        // first, it removes from inner, and if anything is removed, then removeIf() returns true,
        // leading to removing from outer
        outer.removeIf(o -> inner.removeIf(i -> i.equals(o)));

        assertEquals(0, outer.size());
        assertEquals(0, inner.size());
    }

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:

    @Test
    public void testRemoveIfTwoListsInnerHasDuplicates() {
        List<String> outer = new ArrayList<>();
        outer.add("one");
        outer.add("one");
        outer.add("two");
        outer.add("two");
        outer.add("three");
        outer.add("three");
        List<String> inner = new ArrayList<>();
        inner.addAll(outer); // both have duplicates

        // remove all elements from inner(executed twice), then remove from outer
        // but only once! if anything is removed, it will return immediately!!
        outer.removeIf(o -> inner.removeIf(i -> i.equals(o)));

        assertEquals(0, inner.size()); // pass, inner all removed
        assertEquals(0, outer.size()); // will fail, outer has size = 3
    }
淡淡離愁欲言轉身 2024-07-13 22:08:06

由于问题已经得到解答,即最好的方法是使用迭代器对象的remove方法,我将详细介绍抛出错误“java.util.ConcurrentModificationException”的地方。

每个集合类都有一个私有类,它实现 Iterator 接口并提供 next()remove()hasNext() 等方法。

接下来的代码看起来像这样...

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

这里方法 checkForCommodification 实现为

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

因此,如您所见,如果您显式尝试从集合中删除元素。 它会导致 modCountexpectedModCount 不同,从而导致异常 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() 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.

流年已逝 2024-07-13 22:08:06

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

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

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);
}
写给空气的情书 2024-07-13 22:08:06

在这种情况下,一个常见的技巧是(曾经是?)向后退:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

也就是说,我非常高兴您在 Java 8 中有更好的方法,例如 removeIffilter 在流上。

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.

妄断弥空 2024-07-13 22:08:06

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

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

Same answer as Claudius with a for loop:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}
演多会厌 2024-07-13 22:08:06

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

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

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

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}
撩起发的微风 2024-07-13 22:08:06

对于 Eclipse Collections,在 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);

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

此处需要调用 Predicates.cast(),因为默认的 removeIf 方法已添加到 Java 8 中的 java.util.Collection 接口上。

注意: 我是 Eclipse 集合

With Eclipse 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.

一个人练习一个人 2024-07-13 22:08:06

人们声称不能从 foreach 循环迭代的集合中删除。 我只是想指出,这在技术上是不正确的,并准确地描述了该假设背后的代码(我知道OP的问题非常先进,以至于无法了解这一点):

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

这并不是说你不能从迭代的 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:

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.

抠脚大汉 2024-07-13 22:08:06

使用传统的 for 循环

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

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++;   
}
淡淡の花香 2024-07-13 22:08:06

ConcurrentHashMapConcurrentLinkedQueueConcurrentSkipListMap 可能是另一种选择,因为它们永远不会抛出任何 ConcurrentModificationException,即使您删除或添加项目。

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

小瓶盖 2024-07-13 22:08:06

另一种方法是使用 arrayList 的副本仅用于迭代:

List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}

Another way is to use a copy of your arrayList just for iteration:

List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}
如此安好 2024-07-13 22:08:06

ListIterator 允许您添加或删除列表中的项目。 假设您有一个 Car 对象列表:

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

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);
   }
}
浮云落日 2024-07-13 22:08:06

现在,您可以使用以下代码删除

l.removeIf(current -> current == 5);

Now, You can remove with the following code

l.removeIf(current -> current == 5);
画骨成沙 2024-07-13 22:08:06

我知道这个问题对于 Java 8 来说太旧了,但是对于那些使用 Java 8 的人来说,你可以轻松地使用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);

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);
云淡月浅 2024-07-13 22:08:06

Java并发修改异常

  1. 单线程
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        list.remove(it.next()); //throws ConcurrentModificationException
    }
}

解决方案:迭代器remove()方法

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        it.remove()
    }
}
  1. 多线程
  • 复制/转换并迭代另一个集合。 对于小型集合
  • 同步[关于]
  • 线程安全集合[关于]

Java Concurrent Modification Exception

  1. Single thread
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        list.remove(it.next()); //throws ConcurrentModificationException
    }
}

Solution: iterator remove() method

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        it.remove()
    }
}
  1. Multi thread
  • copy/convert and iterate over another one collection. For small collections
  • synchronize[About]
  • thread safe collection[About]
美人如玉 2024-07-13 22:08:06

对于上面的问题我有一个建议。 不需要辅助列表或任何额外的时间。 请找到一个可以以不同方式执行相同操作的示例。

//"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;
}

这将避免并发异常。

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.

梦毁影碎の 2024-07-13 22:08:06
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

如果跳过内部 iterator.next() 调用,则问题是从列表中删除元素后。 它仍然有效! 虽然我不建议编写这样的代码,但它有助于理解其背后的概念:-)

干杯!

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!

帥小哥 2024-07-13 22:08:06

线程安全集合修改示例:

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

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();
            }
        }
    }
}
以可爱出名 2024-07-13 22:08:06

我知道这个问题仅假设一个Collection,而不是更具体的任何List。 但是对于那些阅读这个问题并且确实使用List引用的人来说,您可以使用while循环来避免ConcurrentModificationException(while在其中进行修改)而不是如果您想避免 Iterator (或者如果您想一般性地避免它,或者专门避免它以实现与开始到结束停止不同的循环顺序)在每个元素处[我相信这是 Iterator 本身可以执行的唯一顺序]):

*更新:请参阅下面的注释,澄清类似的操作也可以通过传统-for-loop。

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 1;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i++);

    } else {
        i += 2;
    }
}

该代码中没有 ConcurrentModificationException。

我们看到循环不是从开头开始,也不是在每个元素处停止(我相信Iterator 本身做不到)。

FWIW 我们还看到 getlist 上被调用,如果它的引用只是 Collection (而不是更具体的 >List - Collection 类型) - List 接口包含 get,但 Collection 接口不包含 get 。 如果不是因为这种差异,那么 list 引用可能会是 Collection [因此从技术上讲,这个答案将是直接答案,而不是切线答案]。

FWIWW相同的代码在修改为在每个元素处开始和停止后仍然有效(就像 Iterator 顺序):

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}

I know this question assumes just a Collection, and not more specifically any List. But for those reading this question who are indeed working with a List reference, you can avoid ConcurrentModificationException with a while-loop (while modifying within it) instead if you want to avoid Iterator (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 order Iterator itself can do]):

*Update: See comments below that clarify the analogous is also achievable with the traditional-for-loop.

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 1;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i++);

    } else {
        i += 2;
    }
}

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 on list, which could not be done if its reference was just Collection (instead of the more specific List-type of Collection) - List interface includes get, but Collection interface does not. If not for that difference, then the list reference could instead be a Collection [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):

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}
二智少女 2024-07-13 22:08:06

一种解决方案可能是旋转列表并删除第一个元素以避免 ConcurrentModificationException 或 IndexOutOfBoundsException

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);

One solution could be to rotate the list and remove the first element to avoid the ConcurrentModificationException or IndexOutOfBoundsException

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
落在眉间の轻吻 2024-07-13 22:08:06

试试这个(删除列表中所有等于 i 的元素):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}

Try this one (removes all elements in the list that equal i):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}
魂归处 2024-07-13 22:08:06

您可以使用 while 循环。

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
    Map.Entry<String, String> entry = iterator.next();
    if(entry.getKey().equals("test")) {
        iterator.remove();
    } 
}

You can use a while loop.

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
    Map.Entry<String, String> entry = iterator.next();
    if(entry.getKey().equals("test")) {
        iterator.remove();
    } 
}
烟若柳尘 2024-07-13 22:08:06

在使用 stream().map() 方法迭代列表时,我最终得到了这个 ConcurrentModificationException 。 然而,for(:) 在迭代和修改列表时并没有抛出异常。

这是代码片段,如果对任何人有帮助的话:
在这里,我迭代 ArrayList ,并使用 list.remove(obj) 修改它

 for(BuildEntity build : uniqueBuildEntities){
            if(build!=null){
                if(isBuildCrashedWithErrors(build)){
                    log.info("The following build crashed with errors ,  will not be persisted -> \n{}"
                            ,build.getBuildUrl());
                    uniqueBuildEntities.remove(build);
                    if (uniqueBuildEntities.isEmpty()) return  EMPTY_LIST;
                }
            }
        }
        if(uniqueBuildEntities.size()>0) {
            dbEntries.addAll(uniqueBuildEntities);
        }

I ended up with this ConcurrentModificationException, while iterating the list using stream().map() method. However the for(:) 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)

 for(BuildEntity build : uniqueBuildEntities){
            if(build!=null){
                if(isBuildCrashedWithErrors(build)){
                    log.info("The following build crashed with errors ,  will not be persisted -> \n{}"
                            ,build.getBuildUrl());
                    uniqueBuildEntities.remove(build);
                    if (uniqueBuildEntities.isEmpty()) return  EMPTY_LIST;
                }
            }
        }
        if(uniqueBuildEntities.size()>0) {
            dbEntries.addAll(uniqueBuildEntities);
        }
留蓝 2024-07-13 22:08:06

如果使用 HashMap,在较新版本的 Java (8+) 中,您可以选择 3 个选项中的每一个:

public class UserProfileEntity {
    private String Code;
    private String mobileNumber;
    private LocalDateTime inputDT;
    // getters and setters here
}
HashMap<String, UserProfileEntity> upMap = new HashMap<>();


// remove by value
upMap.values().removeIf(value -> !value.getCode().contains("0005"));

// remove by key
upMap.keySet().removeIf(key -> key.contentEquals("testUser"));

// remove by entry / key + value
upMap.entrySet().removeIf(entry -> (entry.getKey().endsWith("admin") || entry.getValue().getInputDT().isBefore(LocalDateTime.now().minusMinutes(3)));

If using HashMap, in newer versions of Java (8+) you can select each of 3 options:

public class UserProfileEntity {
    private String Code;
    private String mobileNumber;
    private LocalDateTime inputDT;
    // getters and setters here
}
HashMap<String, UserProfileEntity> upMap = new HashMap<>();


// remove by value
upMap.values().removeIf(value -> !value.getCode().contains("0005"));

// remove by key
upMap.keySet().removeIf(key -> key.contentEquals("testUser"));

// remove by entry / key + value
upMap.entrySet().removeIf(entry -> (entry.getKey().endsWith("admin") || entry.getValue().getInputDT().isBefore(LocalDateTime.now().minusMinutes(3)));
老街孤人 2024-07-13 22:08:06

最好的方法(推荐)是使用 java.util.concurrent 包。 经过
使用这个包你可以很容易地避免这个异常。 参考
修改后的代码:

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

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);
}
ぃ弥猫深巷。 2024-07-13 22:08:06

当另一个线程也修改集合时,迭代器并不总是有帮助。 我尝试了很多方法,但后来意识到手动遍历集合要安全得多(向后删除):

for (i in myList.size-1 downTo 0) {
    myList.getOrNull(i)?.also {
       if (it == 5)
          myList.remove(it)
    }
}

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

for (i in myList.size-1 downTo 0) {
    myList.getOrNull(i)?.also {
       if (it == 5)
          myList.remove(it)
    }
}
ら栖息 2024-07-13 22:08:06

如果 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);//

//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
  • 索引循环需要 O(factorial(n)):1090 毫秒,
  • desc 索引:519< /strong> 毫秒---迭代器的最佳时间
  • :1043 毫秒

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
枯叶蝶 2024-07-13 22:08:06

还可以使用递归

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文