阿里的Java编程规约中有一条:【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
整理一下foreach中所出现的坑,以及为什么使用迭代器删除的原因
1、使用foreach循环删除操作出现的问题
首先总结下原因:是因为遍历的时候remove方法并不会修改expectedModCount 的值,在foreach方法属性值检查中抛出异常。
//测试代码
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//直接使用List的remove方法会出现问题
list.forEach((e)->{
if(e.equals((1))){
list.remove(e);
}
});
此时会报类型修改异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1260)
at Test.main(Test.java:16)
首先看ArrayList中forEach的源码:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//最后两行检查集合结构修改次数和期望修改次数是否相等
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
List的remove()方法不会修改expectedModCount的值,只会修改modCount,最后检查不通过抛出异常。
参考文章探索:删除集合中倒数第二个元素时,并不会报异常的问题。
首先看问题代码:
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (Integer integer : list) {
if(integer.equals(1))
list.remove(integer);
}
当删除头尾元素时,会报异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at Test.main(Test.java:15)
可以看出与上面使用foreach方法报的异常信息不同。此时使用增强for循环进行遍历,反编译之后是使用迭代器来进行遍历,迭代器next()方法中的checkForComodification()会检查modCount和expectedModCount是否相等。此时判断会报错。
此时若删除的是3(集合中倒数第二个元素),程序能够正常退出
从此时反编译的字节码中寻找答案:当删除元素3之后,数组的size会减1,再次进入循环之后,迭代器的hasNext()方法会检查集合size和此时的cur是否相等,由于删除3之后,size-1=3 此时的cur也等于3,造成没有遍历到最后一个元素,循环就结束终止。
2、正确的删除方法
//使用迭代器删除
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
if(iterator.next().equals(1)){
iterator.remove();
}
}
总结
迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,该方法主要就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 异常。而remove方法只修改modCount的值。