我写了两个示例代码如下:
private static class Person
{
String name;
public Person(String name)
{
this.name = name;
}
}
public static void main(String[] args)
{
List<Person> l = new ArrayList<Person>();
l.add(new Person("a"));
l.add(new Person("b"));
l.add(new Person("c"));
l.add(new Person("d"));
for(int i = 0;i<l.size();i++)
{
Person s = l.get(i);
System.out.println(s.name);
if(s.name.equals("b"))
l.remove(i);
}
System.out.println("==========================");
for(Person s : l)
System.out.println(s.name);
}
当我运行示例代码时,在控制台上打印结果:
a
b
d
==========================
a
c
d
但是当我按照迭代模型更改代码时:
int i = 0;
for(Person s : l)
{
if(s.name.equals("b"))
l.remove(i);
i++;
}
我得到以下结果:
a
b
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
示例的意思是:在传统的循环模型中,ConcurrentModificationException不是传统的模型发生的吗?
答案 0 :(得分:1)
因为foreach循环实际上使用了迭代器。它相当于
for(Iterator<Person> it; it.hasNext(); ) {
Person s = it.next();
if(s.name.equals("b"))
l.remove(i);
i++;
}
标准集合(如ArrayList,LinkedList等)的迭代器在检测到两次调用next()
之间已修改集合时失败。
第一个循环不使用任何迭代器。它直接要求列表获取其第n个元素。这对于ArrayList来说很好,但是对于LinkedList来说非常慢,它必须在每次迭代时遍历列表的一半,使得迭代O(n ^ 2)而不是O(n)。
答案 1 :(得分:1)
是的,在您原来的for
循环中,您正在按索引访问该值 - 尽管您应该注意到您正在跳过元素c
的检查(因为那时现在位于索引1处。这不会创建ConcurrentModificationException
,因为访问之间没有“上下文” - 修改列表时没有任何内容可以失效。
在增强型for循环版本中,您使用的是迭代器,它保留了您在集合中的位置的上下文。调用l.remove
会使该上下文无效,从而导致错误。
您可以通过在迭代器本身上调用remove
来避免它:
for (Iterator<Person> iterator = l.iterator(); iterator.hasNext(); ) {
Person s = iterator.next();
if (s.name.equals("b")) {
iterator.remove();
}
}
答案 2 :(得分:0)
你的第二个例子隐藏了一个内部使用的迭代器,如果这样的迭代到位,你需要使用具体的迭代器删除,而不是使用“全局”删除方法。在您的第一个示例中,您不创建迭代器,您只是通过某个索引访问单个元素,这不是列表所关心的。但它确实关心创建的,使用过的,活跃的迭代器。