List如何在遍歷時刪除元素

                                     List在遍歷時刪除元素的問題

背景:業務中經常會涉及遍歷list時對集合進行插入或者刪除操作

一、 錯誤方式

先看看下面幾段代碼,1是foreach的方式去遍歷list並刪除元素,2是用迭代器的方式遍歷list並刪除元素,3是下標遍歷

1. foreach 

 

public  void  testDel(){
        List<Integer>  list  = Lists.newArrayList();
        for(int i=1;i<=5;i++){
            list.add(i);
        }
        for(Integer ele : list){
            if(ele == 3)
                list.remove(ele);
        }
    }

2. Iterator

public  void  testDel(){
        List<Integer>  list  = Lists.newArrayList();
        for(int i=1;i<=5;i++){
            list.add(i);
        }
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==3)
                list.remove(integer);
        }
    }

以上兩段代碼的執行結果都是 java.util.ConcurrentModificationException。

在使用ArrayList時,當嘗試用foreach或者Iterator遍歷集合時進行刪除或者插入元素的操作時,會拋出這樣的異常:java.util.ConcurrentModificationException

關於這個異常的原因,看了很多文章,基本上解釋如下:ArrayList的父類AbstarctList中有一個域modCount,每次對集合進行修改(增添、刪除元素)時modCount都會+1。

而foreach的實現原理就是迭代器Iterator,在這裏,迭代ArrayList的Iterator中有一個變量expectedModCount,該變量會初始化和modCount相等,但當對集合進行插入,刪除操作,modCount會改變,就會造成expectedModCount!=modCount,此時就會拋出java.util.ConcurrentModificationException異常,是在checkForComodification方法中,代碼如下:

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

那麼,以上兩種方式都不用呢,用下標遍歷呢?看如下代碼:

3 . 下標遍歷

public void testDel() {
        List<Integer> list = Lists.newArrayList();
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        for(int i=0;i<list.size();i++){
             if(list.get(i)==2){
                list.remove(i);
            }
        }
    }

//結果: list = [1,2,2]

因爲下標是固定死的自增,但list的大小在隨着刪除元素不停的減小,並且後面的元素往前移了1位,所以後面的元素遍歷不到。在i=3時,由於刪了2個元素,size=3,所以循環直接結束。因此這種方式也是不符合預期的。

 

二、解決方法

但業務中經常涉及遍歷時刪除或插入操作。所以如何安全的操作ArrayList呢,解決方法如下!

1.使用迭代器的remove方法

在使用迭代器遍歷時,可使用迭代器的remove方法,因爲Iterator的remove方法中 有如下的操作:

expectedModCount = modCount;

所以避免了ConcurrentModificationException的異常。代碼如下:

public void testDel() {
        List<Integer> list = Lists.newArrayList();
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer == 2)
                iterator.remove();
        }
    }

執行後結果: list = [1]

其實在阿里巴巴Java開發手冊中原話:不要在 foreach 循環裏進行元素的 remove/add 操作。remove 元素請使用 Iterator方式,如果併發操作,需要對 Iterator 對象加鎖。

2.倒序遍歷刪除

public void testDel() {
        List<Integer> list = Lists.newArrayList();
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        for(int i=list.size()-1;i>=0;i--){
             if(list.get(i)==2){
                list.remove(i);
            }
        }
    }

結果: list = [1]

可見,也達到了預期的效果。因爲每次刪除一個元素,list大小-1,但是倒序,循環條件爲i>=0,所以list的size改變並沒有對遍歷造成影響,且元素的前移也不會對倒序遍歷有影響。

所以在對list或者hashmap遍歷時候進行元素刪增操作時,一定要驗證下,我開始想當然的以爲OK,調試才發現前面幾種方式是有問題的,不是異常,就是效果沒達到預期。到線上會出大問題。所以整理了下,推薦最後兩種方式!

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