寫一段代碼在遍歷 ArrayList 時移除一個元素?

今天看面試題的時候,看到這個問題的剖析,覺得挺不錯

寫一段代碼在遍歷 ArrayList 時移除一個元素?
該問題的關鍵在於面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。是使用正確的方式來實現在遍歷的過程中移除元素,而不會出現 ConcurrentModificationException 異常的示例代碼。

於是對ArrayList移除一個元素的相關知識點進行了拓展,查閱到了這些資料:

今天寫了一道題,題目是這樣的:

一個ArrayList對象aList中存有若干個字符串元素,現欲遍歷該ArrayList對象,刪除其中所有值爲"abc"的字符串元素,請用代碼實現。

很簡單,直接上代碼:

public class Test1 {  
  
    public static void main(String[] args) {  
        ArrayList<String> aList = new ArrayList<String>();  
        aList.add("a");  
        aList.add("ab");  
        aList.add("abc");  
        aList.add("abcr");  
        aList.add("abc");  
        aList.add("abcf");  
        aList.add("abc");  
        aList.add("abdc");  
          
        for(int i = 0;i < aList.size();i++){  
            if(aList.get(i).equals("abc")){  
                aList.remove(i);  
                  
              
            }  
        }  
          
        System.out.println(aList);  
    }  
  
}  

輸出結果爲:[a, ab, abcr, abcf, abdc]

也可以使用迭代器來遍歷:

Iterator<String> iter = aList.iterator();  
        while(iter.hasNext()){  
            if(iter.next().equals("abc")){  
                iter.remove();  
                  
            }  

結果與上面相同。

後來改了數據,多加了一個“abc”:

ArrayList<String> aList = new ArrayList<String>();  
        aList.add("a");  
        aList.add("ab");  
        aList.add("abc");  
        aList.add("abc");  //多加的一行  
        aList.add("abcr");  
        aList.add("abc");  
        aList.add("abcf");  
        aList.add("abc");  
        aList.add("abdc");  

然後再用for循環遍歷,結果變爲:

[a, ab, abc, abcr, abcf, abdc] 發現有一個“abc”沒有被移除掉。

然而使用迭代器,答案是對的,所有的“abc”都被移除掉了。

原因:檢查後發現。在for循環裏,當清除掉前一個“abc”後,索引會指向下一個“abc”,然而還做了i++操作,等於直接將這個“abc”跳了過去去執行後面的步驟,從而使它“逃過法網”。

而迭代器不會有這樣的問題是因爲hasNext()方法,原理是指針向後移動,每運行一次it.next(),指針向後移動一次,一個一個的遍歷。

總結:可以在for循環中做一點小處理,如下:

for(int i = 0;i < aList.size();i++){  
            if(aList.get(i).equals("abc")){  
                aList.remove(i);  
                i--;  
              
            }  
        }  

每次清除掉“abc”之後執行i–操作,下一回循環再執行i++操作,就相當於抵消啦。

爲了避免此類問題的出現,儘量還是用迭代器比較好。

還有一種辦法就是:我們知道ArrayList的底層是用數組實現的,如果你刪除了其中一個元素,那麼後邊的元素都會向前移動。所以在遍歷時如果刪除元素,就要小心了。用數組下標進行遍歷,如果需要刪除元素,我們從後向前遍歷,這樣不論有沒有元素刪除,我們都不會遺漏未被遍歷的元素。

這是ArrayList遍歷的時候刪除某元素的兩種辦法,我們還是儘量用迭代器更加的好

下面將針對java.util.ArrayList在foreach循環遍歷時刪除元素的問題

也就是此面試題中所說的ConcurrentModificationException 異常的問題(可能foreach循環只是出現這個異常的一種原因)

查閱了相關的資料,現在分享給大家

ArrayList是java開發時非常常用的類,常碰到需要對ArrayList循環刪除元素的情況。這時候大家都不會使用foreach循環的方式來遍歷List,因爲它會拋java.util.ConcurrentModificationException異常。比如下面的代碼就會拋這個異常:

List list = new ArrayList();  
        list.add("1");  
        list.add("2");  
        list.add("3");  
        list.add("4");  
        list.add("5");  
        for (String item : list) {  
            if (item.equals("3")) {  
                System.out.println(item);  
                list.remove(item);  
            }  
        }  
        System.out.println(list.size());  

那是不是在foreach循環時刪除元素一定會拋這個異常呢?答案是否定的。

見這個代碼:

Listlist=newArrayList();  

       list.add("1");  

       list.add("2");  

       list.add("3");  

       list.add("4");  

       list.add("5");  

       for(Stringitem:list){  

           if(item.equals("4")){  

               System.out.println(item);  

               list.remove(item);  

           }  

       }  

       System.out.println(list.size());  

這段代碼和上面的代碼只是把要刪除的元素的索引換成了4,這個代碼就不會拋異常。爲什麼呢?

接下來先就這個代碼做幾個實驗,把要刪除的元素的索引號依次從1到5都試一遍,發現,除了刪除4之外,刪除其他元素都會拋異常。接着把list的元素個數增加到7試試,這時候可以發現規律是,只有刪除倒數第二個元素的時候不會拋出異常,刪除其他元素都會拋出異常。

好吧,規律知道了,可以從代碼的角度來揭開謎底了。

首先java的foreach循環其實就是根據list對象創建一個Iterator迭代對象,用這個迭代對象來遍歷list,相當於list對象中元素的遍歷託管給了Iterator,你如果要對list進行增刪操作,都必須經過Iterator,否則Iterator遍歷時會亂,所以直接對list進行刪除時,Iterator會拋出ConcurrentModificationException異常

其實,每次foreach迭代的時候都有兩部操作:

iterator.hasNext() //判斷是否有下個元素
item = iterator.next() //下個元素是什麼,並賦值給上面例子中的item變量
hasNext()方法的代碼如下:

public E next() {  
        checkForComodification();  
        try {  
                E next = get(cursor);  
                lastRet = cursor++;  
                return next;  
        } catch (IndexOutOfBoundsException e) {  
                checkForComodification();  
                throw new NoSuchElementException();  
        }  
}  
   
final void checkForComodification() {  
        if (modCount != expectedModCount)  
                throw new ConcurrentModificationException();  
        }  
}  

這時候你會發現這個異常是在next方法的checkForComodification中拋出的,拋出原因是modCount != expectedModCount

modCount是指這個list對象從new出來到現在被修改次數,當調用List的add或者remove方法的時候,這個modCount都會自動增減;
expectedModCount是指Iterator現在期望這個list被修改的次數是多少次。
iterator創建的時候modCount被賦值給了expectedModCount,但是調用list的add和remove方法的時候不會同時自動增減expectedModCount,這樣就導致兩個count不相等,從而拋出異常。

如果想讓其不拋出異常,一個辦法是讓iterator在調用hasNext()方法的時候返回false,這樣就不會進到next()方法裏了。這裏cursor是指當前遍歷時下一個元素的索引號。比如刪除倒數第二個元素的時候,cursor指向最後一個元素的,而此時刪掉了倒數第二個元素後,cursor和size()正好相等了,所以hasNext()返回false,遍歷結束,這樣就成功的刪除了倒數第二個元素了。

破除迷信,foreach循環遍歷的時候不能刪除元素不是絕對,倒數第二個元素是可以安全刪除的~~(當然以上的思路都是建立在list沒有被多線程共享的情況下)

資料轉載於:http://blog.csdn.net/zhuhai__yizhi/article/details/49992321

        http://blog.csdn.net/u011665766/article/details/50697580

        http://blog.csdn.net/hongchangfirst/article/details/49780389

作者:弗蘭隨風小歡
來源:CSDN
原文:https://blog.csdn.net/qq_32575047/article/details/78902254
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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