Foreach刪除元素(ArrayList)報錯分析


普通循環:利用index實現

增強型循環:通過迭代器實現 

示例代碼:

public class ArrayListTest {

    public static void main(String[] args) {
        normalFor(getList());   //普通循環
        iterator(getList());      //增強循環-迭代器
        forEach(getList());     //增強循環-foreach方式
    }

    //普通循環
    private static void normalFor(List<String> list) {
        for (int i = 0 ; i < list.size() ; i++){
            if ("b".equalsIgnoreCase(list.get(i)) || "c".equalsIgnoreCase(list.get(i))){
                list.remove(i);
            }
        }
        System.out.println("normalFor:"+JSONObject.toJSONString(list));
    }

  //增強循環-迭代器
  private static void iterator(List<String> list) {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            String str = (String) iterator.next();
            if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
                iterator.remove();
            }
        }
        System.out.println("iterator:"+JSONObject.toJSONString(list));
    }
   //增強循環-foreach方式
    private static void forEach(List<String> list) {
        for (String str : list){
            if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
                list.remove(str);
            }
        }
        System.out.println("forEach:"+JSONObject.toJSONString(list));
    }

    private static List<String> getList(){
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        return list;
    }
}

 

輸出

normalFor:["a","c","d","e"]
iterator:["a","d","e"]
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 com.qxy.collection.ArrayListTest.forEach(ArrayListTest.java:47)
  at com.qxy.collection.ArrayListTest.main(ArrayListTest.java:21)

Process finished with exit code 1

  

從上邊可以看出

類型 輸出結果
普通循環 正常輸出,結果錯誤
增強循環-迭代器 正常輸出,結果正確
增強循環-foreach 報異常

普通循環

普通循環,底層是數組,在remove操作時,被刪除元素的后邊所有的元素,會往前挪挪一位。咱們還是看圖,比較直觀

 

當第一次刪除時,此時的 i = 1,b正常刪除,c、d、e此時都往前挪了一位,然后執行了 i+1 變成了2,也就是d 的位置,一直往后都沒匹配到c,所以導致c為正常刪除。

 

增強循環-迭代器

在分析之前,我們先來看看反編譯之后的代代碼

public class ArrayListTest {
    ...
    private static void iterator(List<String> list) {
        Iterator iterator = list.iterator();
        while(true) {
            String str;
            do {
                if (!iterator.hasNext()) {
                    System.out.println("iterator:" + JSONObject.toJSONString(list));
                    return;
                }
                str = (String)iterator.next();
            } while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));

            iterator.remove();//不同的地方:調用迭代器的remove方法
        }
    }
    private static void forEach(List<String> list) {
        Iterator var1 = list.iterator();
        while(true) {
            String str;
            do {
                if (!var1.hasNext()) {
                    System.out.println("forEach:" + JSONObject.toJSONString(list));
                    return;
                }
                str = (String)var1.next();
            } while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));

            list.remove(str);//不同的地方:調用list的remove方法
        }
    }  
  ...
} 

 從上邊的代碼來看,迭代器 和 foreach 的方法很類似,唯一的區別就是 remove() 方法

迭代器調用的是  Iterator 類的 remove 方法

foreach調用的是 ArrayList類 的remove方法

那么我們去看下 他們各自 remove方法到底是怎么實現的

迭代器方式,那么需要先看 ArrayList.class

public Iterator<E> iterator() {
    return new Itr();
}

/**
 * An optimized version of AbstractList.Itr
 */
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;  //這個屬性比較重要

    Itr() {}
    ...
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();  //第一步
        try {
            ArrayList.this.remove(lastRet);  //第二步:調用list的remove方法
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;    //第三步:modCount是remove方法去維護更新,由於第一步中校驗 modCount 和 expectedModCount 是否相當等
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException(); 
        } 
    }
    ...
    final void checkForComodification() { 
        if (modCount != expectedModCount) 
            throw new ConcurrentModificationException(); 
    }
}

可以看到,list.iterator() 返回的是一個 Itr對象(ArrayList私有的實例內部類),執行 iterator.remove() 方法時,

第一步:先調用 checkForComodification() 方法,此方法作用:modCount 和 expectedModCount 是否相當

第二步:也就是foreach方式中調用的remove方法,在ArrayList內部的remove方法,會更新modCount屬性

第三步:將更新后的modCount重新賦值給expectedModCount變量,看這里!!!看這里!!!相比於有一個更新操作,才通過了上邊第一步的校驗!!!

到此,你可能還想看看,ArrayList類中的remove方法

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    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

    return oldValue;
}

看到此方法中,有一個modCount++的操作,也就是說,modCount會一直更新變化。

總結:jdk源碼離我們那么近,但是,總是這樣完美的擦肩而過,以后要多啃啃,不要錯過!!!

 


免責聲明!

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



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