1. 程式人生 > >foreach遍歷list刪除元素一定會報錯?

foreach遍歷list刪除元素一定會報錯?

foreach遍歷list集合刪除某些元素一定會報錯嗎,來,先上一段程式碼:
      1)報錯啦 
 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());
理所應當,控制檯就愉快的報出了java.util.ConcurrentModificationException。
這是怎麼回事,然後去看了看這個異常,才發現自己果然還是太年輕啊。
我們都知道增加for迴圈即foreach迴圈其實就是根據list物件建立一個iterator迭代物件,用這個迭代物件來遍歷list,相當於list物件中元素的遍歷託管給了iterator,如果要對list進行增刪操作,都必須經過iterator。

每次foreach迴圈時都有以下兩個操作:

1.iterator.hasNext(); //判讀是否有下個元素

2.item = iterator.next();//下個元素是什麼,並把它賦給item。
首先,我們來看看這個異常資訊是什麼。
public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//此處報錯
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
	final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

可以看到是進入checkForComodification()方法的時候報錯了,也就是說modCount != expectedModCount。具體的原因,是在於foreach方式遍歷元素的時候,是生成iterator,然後使用iterator遍歷。在生成iterator的時候,會儲存一個expectedModCount引數,這個是生成iterator的時候List中修改元素的次數。如果你在遍歷過程中刪除元素,List中modCount就會變化,如果這個modCount和exceptedModCount不一致,就會丟擲異常,這個是為了安全的考慮。看看list的remove原始碼:

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
看,並沒有對expectedModCount進行任何修改,導致expectedModCount和modCount不一致,丟擲異常。所以,遍歷list刪除元素一律用Iterator這樣不會報錯,如下:
Iterator it = list.iterator();
        while(it.hasNext()){
        	if(it.next().equals("3")){
        		it.remove();
        	}
        }
看看Iterator的remove()方法的原始碼,是對expectedModCount重新做了賦值處理的,如下:
public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();


            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;//處理expectedModCount
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
這樣的話保持expectedModCount = modCount相等,就不會報出錯了。
2)是不是foreach所有的list刪除操作都會報出這個錯呢

其實不一定,有沒有發現如果刪除的元素是倒數第二個數的話,其實是不會報錯的,為什麼呢,來一起看看。

之前說了foreach迴圈會走兩個方法hasNext() 和next()。如果不想報錯的話,只要不進next()方法就好啦,看看hasNext()的方法。

public boolean hasNext() {
       return cursor != size;
}
那麼就要求hasNext()的方法返回false了,即cursor == size。其中cursor是Itr類(Iterator子類)中的一個欄位,用來儲存當前iterator的位置資訊,從0開始。cursor本身就是遊標的意思,在資料庫的操作中用的比較多。只要curosr不等於size就認為存在元素。由於Itr是ArrayList的內部類,因此直接呼叫了ArrayList的size欄位,所以這個欄位的值是動態變化的,既然是動態變化的可能就會有問題出現了。

我們以上面的程式碼為例,當到倒數第二個資料也就是”4”的時候,cursor是4,然後呼叫刪除操作,此時size由5變成了4,當再呼叫hasNext判斷的時候,cursor==size,就會呼叫後面的操作直接退出迴圈了。我們可以在上面的程式碼新增一行程式碼檢視效果:

 for (String item : list) {
        	System.out.println(item);
            if (item.equals("4")) {
                list.remove(item);
            }
 }
輸出是:1 2 3 4 
這樣的話就可以看到執行到hasNext()方法就退出了,也就不會走後面的異常了。

由此可以得出,用foreach刪除list元素的時候只有倒數第二個元素刪除不會報錯,其他都會報錯,所以用Iterator啦。

never too late!           

參考:http://rongmayisheng.com/post/%E7%A0%B4%E9%99%A4%E8%BF%B7%E4%BF%A1java-util-arraylist%E5%9C%A8foreach%E5%BE%AA%E7%8E%AF%E9%81%8D%E5%8E%86%E6%97%B6%E5%8F%AF%E4%BB%A5%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0
http://blog.csdn.net/Jywangkeep_/article/details/48754189