foreach循環ArrayList時不能使用ArrayList的增刪方法之源碼簡析

首先,雖然這篇博文的名稱是foreach循環ArrayList時不能使用ArrayList的增刪方法之源碼簡析,但是源碼簡析的主要部分其實在另一篇博文(foreach迭代ArrayList時,真的不能刪除元素嗎?,以下簡稱原博文)。

本文其實是基於原博文做了點小小的擴展。所以在繼續往下閱讀之前,請先閱讀原博文,否則可能會不太能理解我下面在說些什麼!

(爲什麼不再自己從頭分析一遍相關源碼呢?一是因爲我比較懶,但是最主要還是因爲我覺得原博文已經分析得很好很清晰了。)

——————————————————— 手動分割線 ————————————————————

既然看到了這裏,那麼我就默認你已經閱讀過原博文了,接下來就先解答原博文中作者在文末提出的問題。

爲了方便閱讀,這裏再把問題貼出來:
在這裏插入圖片描述
其實吧,我覺得在作者提的問題中已經給出了答案:執行完這次打印,進入下一次迭代時,又產生了checkForComodification異常。

爲什麼又產生了checkForComodification異常?就是因爲又進入了下一次迭代,又先後執行了hasNext()方法和next()方法,在next()方法中做checkForComodification()校驗時,因爲modCount != expectedModCount,所以拋出異常。

那麼,爲什麼循環到倒數第二個元素時,remove一個元素不會拋出異常,而remove兩個元素卻拋出異常呢?

我們來反推一下,拋出ConcurrentModificationException異常是在checkForComodification()校驗中,而checkForComodification()校驗是在next()方法中調用的,而想要調用next()方法,就需要hasNext()方法返回true,即迭代器認爲還有下一個元素。

所以,我們已經可以猜到了,循環到倒數第二個元素時remove一個元素,接下來hasNext()方法返回了false,即判斷已經沒有下一個元素了,所以不會再調用next()方法,也就不會再做checkForComodification()校驗,所以不會拋出異常;而remove兩個元素後執行hasNext()方法返回了true,即判斷還有下一個元素,所以繼續調用next()方法,繼續調用checkForComodification()校驗,進而拋出異常。

上面是我們通過反推後得出的猜想,那麼到底是不是這個原因呢?接下來就看一下hasNext()方法的具體實現。
在這裏插入圖片描述
可以看到hasNext()方法的實現很簡單,僅僅是判斷 cursor 和 size 是否相等,不相等即認爲還有下一個元素,只有相等時才認爲沒有下一個元素了。

其中,size大家都懂,表示的是ArrayList包含的元素的實際個數。cursor表示的是 index of next element to return,我們可以簡單理解爲迭代器指向的下一個元素的下標(簡單理解)。

理解了 cursor 和 size 代表的含義後,我們來舉個栗子:

List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    
    for (String item : list) {
      if ("4".equals(item)) {   // cursor == 4
        list.remove("4");       // size == 4
      }
    }

當迭代到倒數第二個元素時,cursor等於4,當執行完 list.remove(“4”); 後,size等於4。接下來hasNext()方法返回false,迭代結束,沒有拋出異常。

List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    
    for (String item : list) {
      if ("4".equals(item)) {   // cursor == 4
        list.remove("4");       // size == 4
        list.remove("5");       // size == 3
      }
    }

當迭代到倒數第二個元素時,cursor等於4,當執行完 list.remove(“4”); 後,size等於4,而執行完 list.remove(“5”); 後,size等於3。接下來hasNext()方法返回true,繼續迭代,調用next()方法,調用checkForComodification()校驗,拋出異常。

至此,原博文中作者在文末提出的問題回答完畢。

接下來,我們再回頭看看原博文中作者曾提到的一個規律
在這裏插入圖片描述
因爲這個規律只是原博文的作者根據他設計的小實驗的結果提出的,所以這個規律其實並不太準確。現在我們再來繼續完善一下這個規律。

通過前面的分析,我們已經知道在foreach循環ArrayList時調用remove方法後不拋異常的條件是,不能繼續進行迭代,即hasNext()方法返回false,就不會再調用next()方法,不會調用checkForComodification()校驗,不會拋出ConcurrentModificationException異常。也就是說,只要保證調用remove方法後,size == cursor 就可以了。我們再做一個實驗,迭代到倒數第三個元素時,remove掉兩個元素看看會不會拋出異常。

List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    
    for (String item : list) {
      if ("3".equals(item)) {   // cursor == 3
        list.remove("4");       // size == 4
        list.remove("5");       // size == 3
      }
    }

運行代碼後發現沒有拋出異常。

現在,我們可以重新描述一下我們發現的規律了:當迭代到倒數第n個元素時,remove掉n-1個元素,就不會拋出異常。

以上就是foreach循環ArrayList時調用remove方法的全部分析,以上提到的remove方法均爲boolean java.util.List.remove(Object o)。

至於foreach循環ArrayList時調用add方法就沒什麼好分析的了,肯定會拋出異常,現在大家應該想一想就知道原因了。

最後,也是最重要的:
在這裏插入圖片描述
雖然在滿足一定條件的情況下foreach循環ArrayList時調用remove方法不會拋出異常,但是這畢竟是一個坑,大家還是不要給自己挖坑了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章