在ArrayList的foreach循环中添加元素时出现ConcurrentModificationException异常

28

我正在尝试使用foreach循环遍历ArrayList,但当我使用它时,会出现错误,但当我使用普通的for循环时,它可以完美地工作,可能是什么问题?

代码在这里:

for (Pair p2 : R) {
    if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), p2.getSecondElm()));
    else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p2.getFirstElm(), p.getSecondElm()));

    // else
    // There are no transitive pairs in R.
}

这是不起作用的循环,这是一个有效的循环:

for (int i = 0; i < R.size(); i++) {
    if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), R.get(i).getSecondElm()));
    else if ((R.get(i).getSecondElm() == p.getFirstElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (R.get(i).getFirstElm(), p.getSecondElm()));
    //else
    //  There are no transitive pairs in R.
}

使用foreach循环时出现的错误是:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)  
    at set.problem.fourth.PoSet.makeTransitive(PoSet.java:145)  
    at set.problem.fourth.PoSet.addToR(PoSet.java:87)
    at set.problem.fourth.PoSetDriver.typicalTesting(PoSetDriver.java:35)
    at set.problem.fourth.PoSetDriver.main(PoSetDriver.java:13)
3个回答

48
Java集合类是快速失败(fail-fast)的,这意味着如果在使用迭代器遍历集合时,集合被修改,那么iterator.next()将抛出一个ConcurrentModificationException异常。这种情况可能会发生在多线程和单线程环境中。- www.javacodegeeks.com 你不能在for/each循环中修改List,因为它是迭代器的语法糖实现细节。只有在直接使用Iterator时才能安全地调用.remove()
请注意,Iterator.remove是在迭代过程中修改集合的唯一安全方式;如果在迭代进行时以任何其他方式修改底层集合,则其行为是未指定的。- Java集合教程for/each循环中调用.add()会修改内容,背后使用的Iterator会看到并抛出异常。更微妙的问题是,你列出的第二种方式中,.size()每次.add()都会增加,因此你最终将处理所有添加的内容,这可能会导致无限循环,具体取决于输入数据。我不确定这是否符合你的意愿。 解决方案 我会创建另一个ArrayList,并将所有新的内容.add()到其中,在循环之后,使用.addAll()将两个列表合并在一起。这将明确你要做什么,除非你的意图是在添加它们时处理所有新添加的内容。

2014年的解决方案:

始终使用Immutable集合类,并构建新的Immutable集合类,而不是尝试修改单个共享的集合类。这基本上就是我的2012年答案所说的,但我想让它更明确。

Guava非常支持此功能,使用ImmutableList.copyOf()来传递数据。

使用Iterables.filter()将内容过滤为新的ImmutableList,没有共享的可变状态,意味着没有并发问题!


5
你的陈述是错误的,“你无法在for/each循环中修改List”。你可以在foreach循环中修改列表。唯一的注意事项是在修改列表后必须跳出循环。 - Patrick

4
在Java中,for-each循环在底层使用一个Iterator来遍历集合(详见此文章)。如果您在迭代时修改了集合,该迭代器将抛出ConcurrentModificationException异常,请参考此帖子。注意:请保留HTML标记。

2
问题在于你在循环的第一行执行了R.add()操作。
在第一种情况下,你打开了一个对ArrayList的迭代器。当你添加(add)元素并尝试再次进行迭代时,迭代器会注意到数据结构在你使用它的过程中已经发生了变化。
在for循环的情况下,每次只获取一个新元素,并且不存在并发修改的问题,但是随着你添加更多元素,列表的大小在改变。
为了解决这个问题,你可能需要将元素添加到临时位置,然后在循环结束后再将其添加到原始列表中,或者复制初始数据并将其添加到原始数据中。

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