以下Java代码按预期抛出ConcurrentModificationException
:
public class Evil
{
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("sososo");
c.add("ahaaha");
removeLalala(c);
System.err.println(c);
}
private static void removeLalala(Collection<String> c)
{
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}
}
}
但下面的示例(仅在Collection
的内容上有所不同)执行时没有任何异常:
public class Evil {
public static void main(String[] args)
{
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
}
private static void removeLalala(Collection<String> c) {
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}
}
}
打印输出“[lalala]”。为什么第二个示例在第一个示例执行时抛出ConcurrentModificationException
?
答案 0 :(得分:22)
因为无法保证迭代器的快速失败行为。
你得到这个异常是因为除了通过迭代器之外你不能在迭代它时操纵一个集合。
<强>为:强>
// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {
// here, the collection will check it hasn't been modified (in effort to fail fast)
String s = i.next();
if(s.equals("lalala")) {
// s is removed from the collection and the collection will take note it was modified
c.remove(s);
}
}
不可强>
// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {
// here, the collection will check it hasn't been modified (in effort to fail fast)
String s = i.next();
if(s.equals("lalala")) {
// s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration
i.remove();
}
}
现在转到“为什么”:在上面的代码中,注意如何执行修改检查 - 删除将集合标记为已修改,下一次迭代检查是否有任何修改,如果检测到集合已更改则失败。另一个重要的事情是ArrayList
(不确定其他集合)不检查hasNext()
中的修改。
因此,可能会发生两件奇怪的事情:
ArrayList.hasNext()
实际上也会返回false
,因为迭代器的current index
现在指向最后一个元素(以前的第二个元素)持续)。
请注意,这一切都符合ArrayList's documentation:
请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬性保证。失败快速迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
This question提供了有关hasNext()
中并行修改检查不的原因的一些信息,仅在next()
中执行。
答案 1 :(得分:4)
如果查看ArrayList
迭代器(私有嵌套类Itr
)的源代码,您将看到代码中的缺陷。
代码应该是快速失败的,这是通过调用checkForComodification()
在迭代器内部完成的,但hasNext()
没有进行该调用,可能是出于性能原因。< / p>
而hasNext()
只是:
public boolean hasNext() {
return cursor != size;
}
这意味着当您进入列表的倒数第二个元素,然后删除元素(任何元素)时,大小会减小,hasNext()
会认为您和#39;重新使用最后一个元素(你不是),并返回false
,跳过最后一个元素的迭代而没有错误。
OOPS !!!!
答案 2 :(得分:3)
从其他答案中,您知道在迭代集合时删除集合中元素的正确方法是什么。 我在这里给出了基本问题的解释。你的问题的答案在于下面的堆栈跟踪
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.ii4sm.controller.Evil.removeLalala(Evil.java:23)
at com.ii4sm.controller.Evil.main(Evil.java:17)
在堆栈跟踪中,i.next();
行显然会抛出错误。但是当你在集合中只有两个元素时。
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
删除第一个时i.hasNext()
返回false并且永远不执行i.next()
以抛出异常
答案 3 :(得分:2)
你应该直接从iterator
(i)而不是collection
(c)删除;
试试这个:
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
i.remove(); //note here, changing c to i with no parameter.
}
}
编辑:
第一个尝试抛出异常而第二个抛出异常的原因仅仅是因为集合中元素的数量。
因为第一个将经历循环不止一次而第二个将只迭代一次。因此,它没有机会抛出异常
答案 4 :(得分:1)
如果您使用&#34;浏览每个&#34;,则无法从列表中删除环。
您无法从正在迭代的集合中删除某个项目。你可以通过显式使用Iterator并删除那里的项目来解决这个问题。你可以使用Iterator。
如果您使用以下代码,则不会出现任何异常:
private static void removeLalala(Collection<String> c)
{
/*for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}*/
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String st = it.next();
if (st.equals("lalala")) {
it.remove();
}
}
}