public class Alpha {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
al.add("c");
al.add("d");
al.add("e");
Iterator<String> itr = al.listIterator();
while(itr.hasNext()){
al.remove("d"); // Throws a Concurrent Modification @ this line
if(itr.next().equals("d")){
//itr.remove();
al.remove("d"); // No error on this line
}
}
System.out.println(al);
}
}
换句话说,如果将"al.remove("d")放置在if构造中,则不会抛出ConcurrentModificationException,而作为同一行代码,如果放置在if -Construct之外,则会抛出异常。请解释一下!
这是因为当您使用 itr.next()
获取下一个元素时,它将检查修改并检查集合的大小是否已更改。到那时if (modCount != expectedModCount)
条件就会成真。而hasNext()方法仅返回true或false,基于当前游标点。
如果您愿意,itr.next()
然后在列表中调用 move,那么它也将更新 expectedModCount
变量。检查数组列表迭代器的删除方法。
我添加了一行(第 13 行)和一些注释行号(第 18 行、第 19 行和第 21 行)。
现在,ConcurrentModidificationexception 不是在第 18 行抛出的,而是在第 19 行抛出的。但是,是的,由于执行第 18 行,它被抛出。
另外,我相信您一次使用第18行和第21行的代码。它们不会同时出现在代码中。
import java.util.*;
public class HelloWorld{
public static void main(String []args){
ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
al.add("c");
al.add("d");
al.add("e");
al.add("f"); // line 13
Iterator<String> itr = al.listIterator();
while(itr.hasNext()){
al.remove("d"); // line 18
if(itr.next().equals("d")){ // line 19
//itr.remove();
al.remove("d"); // line 21
}
}
System.out.println(al);
}
}
让我们评论第 13 行。所以这基本上是你的代码,有一些额外的注释。
如果我们检查迭代器的 next() 的实现,我们就会知道它在一开始就检查任何修改(从而抛出 CoMo)。之后,它只返回元素。
因此,如果我们从列表中删除元素而不使用第 18 行的迭代器,则会在以下 next() 调用中检测到。但是,如果您在 next() 方法之后删除元素(在第 21 行;现在已注释第 18 行),则只会在随后的 next() 中检测到 CoMo。在您的情况下,我们在打印"e"后用完了元素。所以我们从未执行后续的 next(),也没有收到异常。
现在,如果我们取消注释第 13 行(它添加了一个新元素),那么我们将在第 19 行获得 ConcurrentModidificationexception,即当执行下一个 next() 时。这证明我们将获得 CoMo 异常,只是我们将在再迭代一次后获得它。
重复:遍历列表,避免在循环中删除时出现并发修改异常在 Java 中迭代集合时从集合中删除项
只是你不能在迭代中删除元素。
看看迭代器是如何实现ArrayList
:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
因此,它检查并发修改,使用公共 ArrayList remove 方法删除元素,并增加列表修改的计数器,以便在下一次迭代时不会抛出 ConcurrentModificationException。
就像您正在修改其他人当前正在使用的文件(此处为ArrayList)(此处为迭代循环)。
while(itr.hasNext()){
if(itr.next().equals("d"))
{
//itr.remove();
al.remove("d"); // No error on this line
}
}
System.out.println(al);
使用此代码行。希望这会有所帮助
正如@VimalBera之前的回答中指出的那样:您必须使用 itr.next()
获取下一个元素,然后才能在List
上调用 remove 。
但是,我发现有一些替代方法更适合此用例。第一种是在删除元素时使用迭代器:
public static void main(String[] args) {
List<String> al = new ArrayList<>();
al.add("a");
al.add("b");
al.add("c");
al.add("d");
al.add("e");
for (Iterator<String> itr = al.listIterator(); itr.hasNext(); ) {
String s = itr.next();
if ("d".equals(s)) {
itr.remove();
}
}
System.out.println(al);
}
请注意,使用 for
循环而不是while
循环。这会将迭代器的范围缩小到循环本身(这很好,请始终尽量缩小范围)。
另一种方法是使用 Java 8 Streams,尤其是 filter
方法。但是,这将创建一个全新的列表,并且不会真正修改基础列表,但它提供了一个非常好的编程模型。
public static void main(String[] args) {
List<String> al = Arrays.asList("a", "b", "c", "d", "e");
List<String> filtered = al.stream()
.filter(s -> !s.equals("d"))
.collect(Collectors.toList());
System.out.println(filtered);
}
使用这种方法,您显然可以获得新列表的额外开销,但相反,您可以获得可以将列表视为不可变的,这将在多线程环境中很好地工作。