在循环集合时避免ConcurrentModificationException异常,删除对象的迭代

1297

我们都知道由于 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方法。


1
读者注意:请阅读http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html,它可能有更简单的方法来实现您想要做的事情。 - GKFX
31个回答

10

有人声称在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
    }
}

问题并不在于无法从迭代Colletion中删除, 而是一旦这样做后就无法继续迭代。因此, 上面的代码中使用了break

如果这个答案比较专业,更适合原始线程,那么请谅解,尽管这个线程似乎更加微妙,但被标记为重复,并被锁定。


5

是的,请注意这些都在 java.util.concurrent 包中。该包中一些其他类似/常用的用例类包括 CopyOnWriteArrayListCopyOnWriteArraySet [但不限于此]。 - cellepo
1
实际上,我刚学到尽管那些数据结构对象可以避免ConcurrentModificationException,但在增强型for循环中使用它们仍然可能导致索引问题(例如:跳过元素或IndexOutOfBoundsException...)。 - cellepo

4
另一种方法是仅为迭代使用您的ArrayList的副本:
List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}

2
注意:i不是一个索引,而是对象。也许将其称为obj更合适。 - luckydonald
1
已经在2012年提出过建议:https://dev59.com/A3VC5IYBdhLWcg3wpi98#11201224 在Android上,复制列表是他们通常使用监听器的方法。对于小型列表,这是一个有效的解决方案。 - Slion

3
一个 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);
   }
}

ListIterator 接口中的额外方法(迭代器的扩展)非常有趣 - 特别是它的 previous 方法。 - cellepo

2
现在,您可以使用以下代码进行删除。
l.removeIf(current -> current == 5);

1

我知道这个问题对于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);

1

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. 多线程
  • 复制/转换并迭代另一个集合。对于小集合
  • 同步[关于]
  • 线程安全的集合[关于]

一份更为简洁但更全面的答案。 - Arthur_Morgan
你的第一个示例与第二个示例或原始帖子中的代码不等价。 - user207421

0

线程安全集合修改示例:

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

0
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

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

干杯!


0

我在使用stream().map()方法遍历列表时,遇到了ConcurrentModificationException。然而,在使用for(:)遍历并修改列表时没有抛出异常。

如果对任何人有帮助,这里是代码片段: 我正在遍历一个ArrayList<BuildEntity>,并使用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);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接