为什么不能在for循环中remove掉ArrayList中的对象

先看一段错误的代码,执行的时候不会报错,但是结果和编码人的预期却是不一样的:

List<String> a = new ArrayList<>();
a.add("1");
a.add("2");
for (String temp : a) {
    System.out.println(temp);
    if("1".equals(temp)){
        a.remove(temp);
    }
}

再看一段错误的代码,执行的时候会直接抛出java.util.ConcurrentModificationException:

List<String> a = new ArrayList<>();
a.add("1");
a.add("2");
for (String temp : a) {
    System.out.println(temp);
    if("2".equals(temp)){
        a.remove(temp);
    }
}

奇怪了,为什么第一段代码不会抛出异常,而是执行一次循环就结束了呢,使用javap查看编译好的class文件:

26: aload_1
27: invokeinterface #7,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
32: astore_2
33: aload_2
34: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
39: ifeq          72
42: aload_2
43: invokeinterface #9,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
48: checkcast     #10                 // class java/lang/String
51: astore_3

在执行for循环时,其实是先调用了ArrayList中的iterator方法得到一个Iterator,然后每次循环时,判断Iterator.hasNext,如果hasNext(根据cursor != size判断),则调用Iterator.next,此时就会判断List是否被意外修改(checkForComodification),if (modCount != expectedModCount)则抛出ConcurrentModificationException。如果remove的cursor刚好为size-2,即倒数第二个对象,下次判断hasNext时,cursor == size了,就返回false,认为没有后面的元素了,不会再调用Iterator.next,因此也不会抛出异常,但是也不会把最后一个没有遍历过的元素拿出来进行业务处理。

查看Iterator.remove()的源码,处理基本和ArrayList.remove一致,比较明显的区别就是,Iterator.remove完成后,会调整当前的游标值,继续遍历时就不会导致遍历数据错误,而ArrayList.remove不会调整游标值,如果继续遍历,就会漏掉数据而出错,因此需要抛出ConcurrentModificationException避免被错误使用。