ArrayList在for循環中remove所產生的問題


背景:

剛入職公司的時候,就聽到面試官在面試過程中提問ArrayList在for循環中remove的問題,當時很慶幸自己沒被問到,一年后又一次聽到面試在問這個問題。發現自己還沒有深入研究一下,所以就有了今天這篇文章。

代碼如下:

import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {
    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");
            }
        }
    }
}

以上代碼的執行會導致以下異常:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at future.ArrayListTest.main(ArrayListTest.java:14)

今天通過一種通俗易懂的方式說明代碼異常的原因!

1️⃣list.add

通過查看 list.add 方法,發現有一個變量的值發生了改變,這個變量叫 modCount 。在上面demo中一共執行了四次 add 操作,所以 modCount 的值為4.

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

image-20210421143503040

2️⃣for(String str : list)

for(String str : list) 調用的是 ArrayList 的內部類 Itr ,Itr是對 Iterator 的實現。在 Iterator 開始之前,會先執行 modCount != expectedModCount 。

此時 modCount 和 expectedModCount 的值均為 4 .

3️⃣list.remove("aaa")

先看一下報錯原因,這里是源碼:

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

即 modcount 和 expectedModCount 的值不相等了。看到這里應該有人會想,如果每次執行 remove 后,將 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])) {
                //走的是這個remove
                fastRemove(index);
                return true;
            }
    }
    return false;
}

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

fastRemove 源碼如下:

/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
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
}

通過查看上面的源碼發現 list.remove(E e) 方法執行完成后只執行了 modcount++,並沒有將值賦給 expectedModCount。

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

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();
    }
}

該方法位於內部類 Itr 中。

這也就是說為什么如果 list 在循環中有刪除操作,最好用 Iterator 的方式來做。

結論

簡單總結一下

  • list.remove() 沒有對 expectedModCount 重新賦值
  • iterator.remove() 每次 remove 之后會對 expectedModCount 重新賦值


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM