ArrayList一邊遍歷一邊刪除元素,可能會產生bug!


有時候我們需要對ArrayList進行遍歷,然后根據條件刪除元素,就像下面這樣:

    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        for (String s : list) {
            if ("aa".equals(s)) {
                list.remove("aa");
            }
        }
    }

然后會報如下的錯誤:

我們看一下生成的class文件的反編譯的結果:

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String s = (String)var2.next();
            if ("aa".equals(s)) {
                list.remove("aa");
            }
        }

    }

可以看到,這里遍歷用的是ArrayList實現的迭代器Iterator的hasNext()、next()方法,但是刪除用的卻是ArrayList的remove(Object o)方法。這樣迭代器無法得知ArrayList中元素的變化,比如ArrayList中已經刪了一個元素,后面的元素都向前移動一個位置,原本Iterator位置上的元素被刪除了並且被后面的元素替代,而Iterator不知道,下一次迭代的時候就會以為這個元素已經被遍歷過了而直接跳過。
ArrayList中會對這樣的行為進行監控:
原來ModCount和ExpectedModCount是相等的,而ArrayList.remove(Object o)每被執行一次ModCount都會加1:

Iterator中會驗證ModCount和ExpectedModCount是否相等:

如果不相等就會拋出ConcurrentModificationException異常

正確的做法:

看一下這里的iterator.remove()做了什么事:

其實就是在調用list.remove(Object o)的時候加上一句"expectedModCount = modCount;",這樣在下一次迭代的時候他們的值就相等了。

補充:
可以先用javac將java編譯成.class文件(javac [-encoding utf-8] javaName.java),然后在開發工具中打開,可以查看.class文件的反編譯內容;或者直接在開發工具中查看class文件(反編譯后的文件)。用以查看一些“不可見”方法的具體實現:

for (String s : list) {
    if ("aa".equals(s)) {
        list.remove("aa");
    }
}

Iterator var2 = list.iterator();
while(var2.hasNext()) {
    String s = (String)var2.next();
    if ("aa".equals(s)) {
        list.remove("aa");
    }
}

注:如果用jdk自帶的javap指令,即javap className,可能看到的源代碼的原貌只剩下了方法名


免責聲明!

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



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