foreach遍歷list刪除元素一定會報錯?
阿新 • • 發佈:2019-01-11
foreach遍歷list集合刪除某些元素一定會報錯嗎,來,先上一段程式碼:
1)報錯啦
這是怎麼回事,然後去看了看這個異常,才發現自己果然還是太年輕啊。
我們都知道增加for迴圈即foreach迴圈其實就是根據list物件建立一個iterator迭代物件,用這個迭代物件來遍歷list,相當於list物件中元素的遍歷託管給了iterator,如果要對list進行增刪操作,都必須經過iterator。
首先,我們來看看這個異常資訊是什麼。
2)是不是foreach所有的list刪除操作都會報出這個錯呢
其實不一定,有沒有發現如果刪除的元素是倒數第二個數的話,其實是不會報錯的,為什麼呢,來一起看看。
這樣的話就可以看到執行到hasNext()方法就退出了,也就不會走後面的異常了。
http://blog.csdn.net/Jywangkeep_/article/details/48754189
1)報錯啦
理所應當,控制檯就愉快的報出了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());
這是怎麼回事,然後去看了看這個異常,才發現自己果然還是太年輕啊。
我們都知道增加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原始碼:
看,並沒有對expectedModCount進行任何修改,導致expectedModCount和modCount不一致,丟擲異常。所以,遍歷list刪除元素一律用Iterator這樣不會報錯,如下: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; }
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://blog.csdn.net/Jywangkeep_/article/details/48754189