ArrayList 中 modCount 的作用

2,874 阅读2分钟

在 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。