在使用迭代器遍历集合时,为何不能使用集合的remove方法?
一、发现问题
在看到数据结构与算法分析第三章时,看到这样一段代码
public static void removeEvens(List<Integer> list) {
for (Integer x : list) {
if (x % 2 == 0) {
list.remove(x);
}
}
}
这段代码想完成的功能很清楚,就是要移除list集合中所有的偶数,但是这段程序会报错,抛出
ConcurrentModificationException异常。这是为什么呢?书上是这样说的,编译器在看到一个实现了Interator接口的对象的增强for循环时,会自动地重写,变成使用迭代器来遍历集合。
上面for循环代码实际上会被翻译为:
Iterator<Integer> iterator=list.iterator();
while(iterator.hasNext()){
Integer element=iterator.next();
if(element%2==0){
list.remove(element);
}
}
问题就很清楚了,在使用迭代器遍历集合时,如果使用了集合的remove方法,则会抛出ConcurrentModificationException异常。这是为什么呢?书上只说了在使用Iterator时,如果对正在迭代的集合进行结构上的改变,即使用集合的add,remove,clear方法是,迭代器就不再合法。(不好理解)
二、解决问题
网上也有些写的挺好的博客,这里自己再进行整理一下,以加深理解。
Iterator<Integer> iterator=list.iterator();
因为list是一个集合对象,它实现了Iterable接口,而该接口要求集合实现iterator的方法,返回值是一个Iterator类型。
public interface Iterator<AnyType>{
boolean hasNext();
AnyType next();
void remove();
在ArrayList中,iterator方法会返回一个Itr类型的对象。
public Iterator<E> iterator() {
return new 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
//修改次数--->用于检测在迭代期间被修改的情况,expectedModeCount初始值是modCount,通过expectedModeCount与modCount的比较来进行错误检测
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//错误检测
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//elementData存在于外部类ArrayList中,它以数组形式存储着集合元素,通过外部类的隐式引用来使用elementData
Object[] elementData = ArrayList.this.elementData;
//如果此时的游标大于集合元素的长度
if (i >= elementData.length)
throw new ConcurrentModificationException();
//游标+1
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//迭代器的remove方法,实际上要靠集合的remove方法来实现,
//值的注意的是:它对游标进行了修改,并且对象expectedModCount进行了修正
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//错误检测方法,通过比较modCount与expectedModCount是否一致,不一致则抛出异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
迭代器的remove方法与集合的remove方法,最大的不同是,迭代器的remove方法中包括对游标和expectedModCount的修正。
因为Iterator是在一个独立的线程中工作的,它在new Itr()进行初始化时,会记录当时集合中的元素,可以理解为记录了集合的状态,在使用集合的Remove方法对集合进行修改时,被记录的集合状态并不会与之同步改变,所以在cursor指向下一个要返回的元素时,可能会发生找不到的错误,即抛出ConcurrentModificationException异常。
很明显,如果使用迭代器提供的remove方法时,会对cursor进行修正,故不会出现错误,此外,还会修正expectedModCount,通过它来进行错误检测(迭代过程中,不允许集合的add,remove,clear等改变集合结构的操作)。
The world is such small,it’s like when you turn around,you don’t know who you will see. The world is so big,as if when you turn around,you never know who will disappear.