我有一个非常基本的问题。
我创建了简单的ArrayList
,我在使用for-each循环迭代时删除了该项。它给了我java.util.ConcurrentModificationException
,因为我在迭代时无法移除项目,但当我取消评论if
条件时,它可以正常工作。
任何人都可以通过这种方式向我解释每个人的工作方式。
ArrayList<String> list1 = new ArrayList<String>();
list1.add("Hello");
list1.add("World");
list1.add("Good Evening");
for (String s : list1) {
//if (s.equals("World")) {
list1.remove(1);
//}
}
如果我将其更改为list1.remove(2);
或list1.remove(0);
,那么其工作正常。
注意:这是示例代码,我知道它可以使用Iterator
正常工作。这个问题的唯一目的是知道方法remove()
如果条件未被注释,无论你从列表中删除什么索引,如何完美地工作。
答案 0 :(得分:5)
该列表有一个名为modCount
的变量,表示&#34;修改计数&#34;。每当您致电remove
(或执行其他结构修改)时,它会递增modCount
。
如果要在不告知迭代器的情况下添加或删除元素,迭代器就无法跟踪其在列表中的位置。因此,作为安全检查,在迭代开始时,迭代器会记录modCount
,并将其保存为expectedModCount
。当从迭代器中读取每个项时,迭代器会检查以确保modCount
仍然等于期望值,并且如果它没有,则抛出异常。
通常,如果在迭代期间不安全地修改列表,这将成功导致抛出异常。但是,在启用if
语句的情况下,这还不够。在您的代码阅读"World"
后,该项目将被删除,因此该列表现在包含["Hello", Good Evening"]
。迭代器仍然在位置1(现在包含"Good Evening"
),当它尝试读取下一个项目时,它发现它现在已经到达列表的末尾,所以它不会打扰检查modCount
。因此,也不例外。
请注意ConcurrentModificationException documentation中的警告:&#34;一般来说,在存在非同步并发修改的情况下,无法做出任何硬保证。失败快速操作会尽最大努力抛出ConcurrentModificationException。&#34;
即使在这种情况下没有发生抛出异常,代码仍然是错误的。要在迭代时删除元素,必须使用迭代器自己的remove
方法:
for (Iterator<String> it = list1.iterator(); it.hasNext();) {
String s = it.next();
if (s.equals("World")) {
it.remove();
}
}
这样,迭代器知道列表已经改变,仍然可以正确迭代。
或者,您可以从列表的临时副本进行迭代:
for (String s : new ArrayList<>(list1)) {
if (s.equals("World")) {
list1.remove(...);
}
}
虽然在这个简单的例子中,你甚至不需要这样做;你可以写:
list1.remove("World");
答案 1 :(得分:0)
您还可以使用基于索引的删除。此解决方案的缺点是在每次循环迭代期间都会对list1.size()进行求值。积极的一点是,通过索引从List中删除项目的速度更快。
for (int i = 0; i < list1.size(); /* i incremented in loop body */) {
if ("World".equals(list1.get(i))) {
list1.remove(i);
}
else {
i++;
}
}
答案 2 :(得分:0)
使用Iterator并调用remove():
Iterator<String> iter = list1.iterator();
while (iter.hasNext()) {
String str = iter.next();
if (someCondition)
iter.remove();
}