先看一段错误的代码,执行的时候不会报错,但是结果和编码人的预期却是不一样的:
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避免被错误使用。