在AbstractList中,有一个全局变量madCount,记录了结构性改变的次数。结构性改变指的是那些修改了列表大小的操作,在迭代过程中可能会造成错误的结果。
madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用,当进行next()、remove()、previous()、set()、add()等操作时,如果madCount的值意外改变,那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。
如果希望提供快速失败(fail-fast)机制,那么其子类就应该在add(int, E)、remove(int)等结构性改变的方法中将madCount变量自增,否则可以忽略该变量。
在开发中可能遇到过这样的代码:
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
for (String str : strList) {
if ("b".equals(str)) {
strList.remove(str);
}
}
在执行了remove操作之后就会抛出ConcurrentModificationException,原因是加强for循环利用了迭代器进行遍历,遍历时发生了异常并抛出。
在ArrayList中就继承了AbstractList并在每个结构性改变的方法中让madCount变量自增1,并且实现了自己的迭代器:
private class Itr implements Iterator<E> {
// Android-changed: Add "limit" field to detect end of iteration.
// The "limit" of this iterator. This is the size of the list at the time the
// iterator was created. Adding & removing elements will invalidate the iteration
// anyway (and cause next() to throw) so saving this value will guarantee that the
// value of hasNext() remains stable and won't flap between true and false when elements
// are added and removed from the list.
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
在next()方法中,判断了当前的modCount跟初始化Itr时的expectedModCount是否相同,不同则说明列表数据进行了结构性改变,此时就会抛出ConcurrentModificationException。
因此在遍历中删除元素的正确做法应该是使用Iterator:
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("c".equals(str)) {
iterator.remove();
}
}
或者使用线程安全的CopyOnWriteArrayList。