设想你有一个简单的需求:在一个ArrayList中的某个位置插入一个新的元素,于是你写下了如下代码
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
Iterator<String> it = list.iterator();
while(it.hasNext()){
if("cc".equals(it.next())){
list.add("kk"); // 插入一个新的元素
}
}
发现报错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at org.zyf.Main.main(Main.java:20)
说明在调用it.next()的时候抛出了ConcurrentModificationException异常.
ArrayList类继承了抽象类AbstractList,所以ArrayList拥有一个成员变量modCount,记录了对ArrayList的操作次数:
// AbstractList.java
/**
* The number of times this list has been structurally modified.
...
...
*/
protected transient int modCount = 0;
每次对 ArrayList 的 add 和 remove 方法的成功调用,都会使 modCount 自增。
阅读源码可以发现,在对 ArrayList 使用迭代器进行迭代的时候,使用的其实是实现了 Iterater 接口的 ArrayList 内部类 Itr,
// ArrayList.java
public Iterator<E> iterator() {
return new Itr();
}
...
private class Itr implements Iterator<E> {
...
}
迭代时,我们调用 Itr.next(),而这个方法会调用 checkForComodification() 方法:
// ArrayList.Itr
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];
}
再看看 checkForComodification() 做了什么:
// ArrayList.Itr
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法检查了 modCount 和 expectedModCount 的一致性,如果不一致,则抛出 ConcurrentModificationException 异常,这里就是报错的源头。
而 Itr 进行初始化的时候,其成员变量 expectedModCount 正是被初始化为了modCount 的值:
// ArrayList.java
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;
...
}
这说明在创建迭代器时expectedModCount和modCount就是一致的。
但是!在创建迭代器之后,任何对ArrayList的直接修改(绕过迭代器对其进行修改),都不会影响 expectedModCount!
文章开头代码中直接使用List.add()加入新元素,只会修改modCount的值,expectedModCount 并不会发生变化,所以无法通过一致性检查,导致了异常。
事情让一个“人”做 —— 引入新的迭代器:ListIterator
,该迭代器提供了安全的add方法,可使对ArrayList的修改通过一致性检查。
// ArrayList.ListItr,实现了ListIterator接口
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount; // 更新expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
迭代和添加操作都是靠ListIterator
来完成的:
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
if("cc".equals(it.next())){
it.add("kk");
}
如果是想要迭代时删除元素,不必使用ListIterater,调用Iterator的remove即可
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
// 移除"cc"
Iterator<String> it = list.iterator();
while(it.hasNext()){
if("cc".equals(it.next())){
it.remove();
}
}