ArrayList 迭代器學習筆記


我們先來看一段代碼:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
    if ("str1".equals(s)) {
        list.remove(s);
    }
}

這段代碼看起來好像沒有什么問題,但是如果我們運行,就會拋出ConcurrentModificationException異常。

其實這不是特例,每當我們使用迭代器遍歷元素時,如果修改了元素內容(添加、刪除元素),就會拋出異常,由於 foreach 同樣使用的是迭代器,所以也有同樣的情況,大家可以自行試一下。我們來通過源碼探究一下這個現象的根本原因。

ArrayList 源碼閱讀

下面是 ArrayList 的部分源碼,可以明顯的看到共有兩個remove()方法,一個屬於 ArrayList 本身,還有一個屬於其內部類 Itr。

public class ArrayList<E> {
​
    void remove() {
        modCount++;  // 繼承自AbstractList的屬性,保存對其中元素的修改次數,每次增加或刪除時加1
        // 具體刪除操作代碼
        //...
    }
  
    public Iterator<E> iterator() {
        return new Itr();
    }
​
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        // 在創建迭代器時將當前ArrayList的修改次數賦值給 expectedModCount 保存
        int expectedModCount = modCount;
​
        public boolean hasNext() {
            return cursor != size;
        }
​
        @SuppressWarnings("unchecked")
        public E next() {
            // 檢查當前所在的 ArrayList 的 modCount 是否與創建 Itr 時的值一致,
            // 也就是判斷獲取了Itr迭代器后 ArrayList 中的元素是否被 Itr 外部的方法改變過。
            checkForComodification();
            // 具體的獲取下一個元素的代碼
            // ...
        }
​
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            // 同 next 中的 checkForComodification 方法
            checkForComodification();
​
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                // Itr 內部的刪除元素操作,會更新 expectedModCount 值,而外部的則不會
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
​
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

ArrayList 類中有一個 modCount 屬性,這個屬性是繼承子AbstractList,其保存了我們對 ArrayList 進行的的操作次數,當我們添加或者刪除元素時,modeCount 都會進行對應次數的增加。

迭代器迭代時外部方法添加或刪除元素

在我們使用 ArrayLis 的 iterator() 方法獲取到迭代器進行遍歷時,會把 ArrayList 當前狀態下的 modCount 賦值給 Itr 類的 expectedModeCount 屬性。如果我們在迭代過程中,使用了 ArrayList 的 remove()add()方法,這時 modCount 就會加 1 ,但是迭代器中的expectedModeCount 並沒有變化,當我們再使用迭代器的next()方法時,它會調用checkForComodification()方法,即

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

發現現在的 modCount 已經與 expectedModCount 不一致了,則會拋出ConcurrentModificationException異常。

迭代器迭代時,使用迭代器的方法添加或刪除元素

但是如果我們使用迭代器提供的remove()方法,由於其有一個操作:expectedModCount = modCount;,會修改expectedModCount 的值,所以就不會存在上述問題。

總結

當我們使用迭代器迭代對象的時候,不要使用迭代器之外的方法修改元素,否則會報異常。如果我們要在迭代器迭代時進行修改,可以使用迭代器提供的刪除等方法。或者使用其他方法遍歷修改。

 


注意:

特殊情況下在迭代器過程中使用 ArrayList 的刪除方法不會報異常,就是 只刪除倒數第二個元素的時候,代碼如下:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
    if ("str2".equals(s)) { // 必須只能是倒數第二個元素,這樣才不會拋異常
        list.remove(s);
    }
}

其原因是迭代器的hasNext()方法:

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

在只刪除了倒數第二個元素的時候,cursor 會與 size 相等,這樣hasNext()方法會返回 false ,結束迭代,也就不會進入 next()方法中,進而執行checkForComodification()檢查方法拋出異常。

 

 


免責聲明!

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



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