簡單說明:ArrayList 在 For 循環中進行刪除而產生異常的緣由

常常會有人這麼對 list 進行遍歷,錯而不自知。java

示例代碼以下:this

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    list.add("ddd");

    for (String str : list) {
        if ("aaa".equals(str)) {
            list.remove("aaa");
        }
    }
}

以上代碼執行致使的報錯信息以下:spa

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 demo.service.impl.test.main(test.java:14)

網上有不少博客對此都作了說明,這篇文章經過比較淺顯易懂的方式說明報錯產生的緣由。code

1、list.add

在 list.add 代碼執行時,有一個變量發生改變了,那就是 modCount。在代碼中 list.add 共執行4次,因此 modCount 的值爲 4。element

注:list 的 add()remove() 和 clear() 都會改變 modCount 值。rem

2、for (String str : list)

for (String str : list) 調用的是 ArrayList 中內部類 ItrItr 是對 Iterator 的實現。而在 Iterator 開始前,會先執行 int expectedModCount = modCountget

此時 expectedModCount 和 modCount 均爲 4源碼

3、list.remove("aaa")

在此處先看一下會報錯的緣由,如下是源碼:博客

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

即 modCount 與 expectedModCount 不相等了,因此報錯。it

有人可能會跟我有同樣的想法,爲何 list.remove("aaa") 時,不把 expectedModCount = modCount 從新賦值一次。實際上是有的,只是調用的方法錯了。

例子中 list.remove("aaa") 調用的 remove 源碼以下:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                // 示例中調用的是此處的 fastRemove
                fastRemove(index);
                return true;
            }
    }
    return false;
}

而使 modCount 的值改變的是其中的 fastRemove 方法。

fastRemove 源碼以下:

private void fastRemove(int index) {
    // 此處 modCount + 1
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

而真正使 expectedModCount = modCount 執行的源碼以下:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
    }
}

此代碼在內部類 Itr 中。

這也就是爲何會說,若是 list 在循環中有刪除操做,最好用 iterator 迭代的方式去作。

4、總結

簡單總結一下

  • list.remove() 沒有對 expectedModCount 從新賦值
  • iterator.remove() 對 expectedModCount 從新賦值

建議你們跟蹤一下源代碼,代碼量很少,也很容易理解。

附錄:內部類 Itr 源碼(不長,分分鐘看完)

private class Itr implements Iterator<E> {
    /**
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = 0;

    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -1;

    /**
     * The modCount value that the iterator believes that the backing
     * List should have.  If this expectation is violated, the iterator
     * has detected concurrent modification.
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

 

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索