foreach循環(加強for循環)的坑 - ConcurrentModificationException

        最近在看《阿里巴巴Java開發手冊》時,(五)集合處理-7【強制】不要在foreach循環裏進行元素的remove/add操做。這一條規則中,有一段很是有意思的代碼java

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");

for (String item : list) {
    if ("1".equals(item)) {
        list.remove(item);
    }
}複製代碼

執行這段代碼,沒有任何問題,foreach循環不會拋出任何異常,並且元素移除成功。數組

可是,當咱們把if ("1".equals(item))改爲if ("2".equals(item))時,程序會拋出ConcurrentModificationException。以下圖所示:測試


經過分析foreach實現機制以及拋出的代碼行數,很容易得知其緣由。this

foreach實現機制

        咱們知道,foreachJava的一個語法糖,經過反編譯測試類生成的代碼,咱們知道它其實是由Iterator實現的。反編譯的結果以下:spa

List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();

while (var2.hasNext()) {
    String item = (String)var2.next();
    if ("2".equals(item)) {
        list.remove(item);
    }
}複製代碼

        結合程序拋出的異常棧信息,咱們能夠得知,異常是在String item = (String)var2.next();這一行拋出的(只有這一行調用了next()操做)。code

異常緣由

        找到報錯的代碼行,咱們再來分析報錯的緣由。既然在String item = (String)var2.next();報錯,說明while循環的條件是經過了。也就是說,咱們在移除了最後一個元素後,迭代器居然並未感知到,hasNext()依然返回true。咱們來看看ArrayList.IteratorhasNext()是如何實現的:cdn

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;   // 修改次數,modCount來自於外部的ArrayList類;
                                       // 而expectedModCount則由Itr記錄
    ...
    public boolean hasNext() {
        return cursor != size;   // size來自於外部的ArrayList類
    }
    ...
}複製代碼

    ArrayList.iterator().hasNext()的實現很簡單,就是判斷當前下標是否與數組大小相同。這就說明,當刪除ArrayList最後一個元素時,cursor != size。爲何會這樣呢?咱們再來看一下next()的實現:blog

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;   // 修改次數,modCount來自於外部的ArrayList類;
                                       // 而expectedModCount則由Itr記錄
    ...
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size) 
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
    ...
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}複製代碼

        咱們看cursor值 的變化,實際上,cursor的值在每次進行元素值對比前,就已經跳到下一個位置。當咱們刪除第1個元素後,cursor的值是1,與當前ArrayList.size(1)是相等的,hasNext()返回falsewhile循環結束;而當咱們刪除第2個元素時,cursor的值是2,與當前ArrayList.size(1)不相等,所以hasNext()返回trueelement

        咱們再看next()爲何拋出異常。ArrayList.Itrnext()操做中,首先檢查modCount是否等於expectedModCount,若是不相等,則拋出ConcurrentModificationException,程序中的異常也正是在此處拋出的。expectedModCountArrayList.Itr維護,而咱們程序中用ArrayList.remove()移除元素,修改的是ArrayList.modCount,這兩個值顯然不相等(expectedModCount == 0modCount == 1)。正是因爲hasNext()錯誤地返回了true,致使咱們調用next()而拋出了異常。開發

結論

        從上文的分析中咱們能夠得出結論,因爲foreach語法糖的特性,它並非嚴格意義上的java語法,不能像Java標準語法那樣靈活地使用。從其生成的代碼來看,foreach循環不能進行元素的修改操做,而只能進行元素的遍歷操做。

相關文章
相關標籤/搜索