1. 程式人生 > >ArrayList在foreach刪除倒數第二個元素不拋並發修改異常的問題

ArrayList在foreach刪除倒數第二個元素不拋並發修改異常的問題

就會 string style 現在 util 元素 fbo 兩個 exc

平時我們使用ArrayList比較多,但是我們是否知道ArrayList在進行foreach的時候不能直接通過list的add或者move方法進行刪除呢,

原因就是在我們進行foreach遍歷的時候,其實底層原理就是使用了 iterator 叠代器進行操作的,我們在foreach中使用list的add 或者 move 方法;會導致並發修改異常拋出;

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叠代的時候都有兩部操作:

  1. iterator.hasNext() //判斷是否有下個元素
  2. 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沒有被多線程共享的情況下)

ArrayList在foreach刪除倒數第二個元素不拋並發修改異常的問題