遍历 Collection 时删除元素
其实标题我想用《为什么foreach边循环边移除元素要用Iterator?》可是太长。
不用Iterator,用Collection.remove(),会报ConcurrentModificationException错误。
for(Integer i : list) {
list.remove(i); //Throw ConcurrentModificationException
}
其实使用foreach的时候,会自动生成一个Iterator来遍历list。不只是remove,使用add、clear等方法一样会出错。
拿ArrayList来说,它有一个私有的Iterator接口的内部类Itr:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
//sevrval methods
}
使用Iterator来遍历ArrayList实际上是通过两个指针来遍历ArrayList底层的数组:cursor是下一个返回的元素在数组中的下标;lastRet是上一个元素的下标。还有一个重要的expectedModCount使用的是ArrayList的modCount的(modCount具体是什么意思下文会提到)。
从Itr的实现来看,有三种情况会抛出ConcurrentModificationException:
- cursor超出了数组的最大下标
- expectedModCount不等于modCount
- 删除元素最终还是调用ArrayList的remove方法,此方法可能会抛出IndexOutOfBoundsException
expectedModCount不等于modCount
开头所说的问题正是是第二种情况下出现的。modCount简单说记录了Collection被修改的次数:添加或者删除元素。
假如在foreach循环中删除元素,且此时modCount等2:
- 循环开始创建新Itr实例,expectedModCount=modCount=2
- 使用ArrayList.remove()删除元素,modCount加1
- 继续调用next()方法指向下一个元素,此时检查expectedModCount是否等于modCount,不等则抛ConcurrentModificationException
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
上面提到Iterator的实现中删除元素实际调用的还是ArrayList.remove()方法,为什么不会抛错?
Itr的remove方法在调用ArrayList.remove()之后,会更新expectedModCount。
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();
}
}