迭代器Remove问题

今天遇到这样一段代码,觉得很有意思,是一个清空元素的操作,当时还纳闷儿为啥不用 clear() 呢,原来就是一个 puzzle 。

1
2
3
4
5
6
7
8
ArrayList<Integer> arr = new ArrayList<Integer>();
arr.add(1);
arr.add(1);
arr.add(3);
Iterator<Integer> it = arr.iterator();
while (it.hasNext()) {
it.remove();
}

乍一看,觉得是可以的,但是直接运行就出抛出异常.

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:864)
at com.cvte.proxy.App.main(App.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

这个问题和常见的 ConcurrentModificationException 不太一样。产生 ConcurrentModificationException 问题的原因主要是,在通过迭代器遍历容器元素列表的时候容器发生了结构性变化,导致迭代器内部维护的索引位置失效。

那么,只能来翻看一下ArrayList的源码了,找到迭代器的remove()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

通过错误信息可以看到,之所以抛出 IllegalStateException 就是因为 lastRet 小于 0.
继续看一下Iterator的内部实现

1
2
3
4
5
6
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; //判断是否发生了结构变化,如果不一致就会抛出, ConcurrentModificationException 异常
...
}

原来 lastRet 表示的是最后一个返回的索引位置,如果没有,就为-1.所以,想一想也是合理的,你都没有返回元素,你让我删个啥玩意儿呢?

所以,如果一定要这么搞,那么在调用 remove() 之前就需要先调用一次 next() 让它有东西可以删除。

1
2
3
4
5
6
7
8
9
ArrayList<Integer> arr = new ArrayList<Integer>();
arr.add(1);
arr.add(1);
arr.add(3);
Iterator<Integer> it = arr.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}