为什么 ConcurrentModificationException 只发生在迭代循环中
Why ConcurrentModificationException occurred only at iterate loop
我写了两个示例代码如下:
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不会发生在传统的for模型中??
因为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)。
是的,在您原来的 for
循环中,您通过索引访问值 - 尽管您应该注意您正在跳过对元素 c
的检查(因为到那时was at index 2 is now at index 1).这不可能创建 ConcurrentModificationException
,因为在访问之间没有 "context" - 当您修改列表时没有任何内容会失效。
在增强型 for 循环版本中,您使用的是 迭代器,它保留您在集合中所处位置的上下文。调用 l.remove
会使该上下文无效,因此会出现错误。
您可以通过在迭代器本身上调用 remove
来避免它:
for (Iterator<Person> iterator = l.iterator(); iterator.hasNext(); ) {
Person s = iterator.next();
if (s.name.equals("b")) {
iterator.remove();
}
}
您的第二个示例隐藏了一个内部使用的迭代器,如果存在这样的迭代器,您需要使用具体的迭代器进行删除,而不是使用 "global" 删除方法。在您的第一个示例中,您没有创建迭代器,您只是通过某个索引访问单个元素,这与列表无关。但它确实关心创建的、使用的、活动的迭代器。
我写了两个示例代码如下:
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不会发生在传统的for模型中??
因为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)。
是的,在您原来的 for
循环中,您通过索引访问值 - 尽管您应该注意您正在跳过对元素 c
的检查(因为到那时was at index 2 is now at index 1).这不可能创建 ConcurrentModificationException
,因为在访问之间没有 "context" - 当您修改列表时没有任何内容会失效。
在增强型 for 循环版本中,您使用的是 迭代器,它保留您在集合中所处位置的上下文。调用 l.remove
会使该上下文无效,因此会出现错误。
您可以通过在迭代器本身上调用 remove
来避免它:
for (Iterator<Person> iterator = l.iterator(); iterator.hasNext(); ) {
Person s = iterator.next();
if (s.name.equals("b")) {
iterator.remove();
}
}
您的第二个示例隐藏了一个内部使用的迭代器,如果存在这样的迭代器,您需要使用具体的迭代器进行删除,而不是使用 "global" 删除方法。在您的第一个示例中,您没有创建迭代器,您只是通过某个索引访问单个元素,这与列表无关。但它确实关心创建的、使用的、活动的迭代器。