遍历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();
    }
}

comments powered by Disqus