foreach迭代ArrayList时,真的不能删除元素吗?
ArrayList是java開發時非常常用的類,常碰到需要對ArrayList循環刪除元素的情況。這時候大家都不會使用foreach循環的方式來遍歷List,因為它會拋java.util.ConcurrentModificationException異常。比如下面的代碼就會拋這個異常:
List<String>?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"))?{list.remove(item);} } System.out.println(Arrays.toString(list.toArray()));那是不是在foreach循環時刪除元素一定會拋這個異常呢?答案是否定的。
見這個代碼:
List<String>?list?=?new?ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5");for?(String?item:?list)?{if?(string.equals("4"))?{list.remove(item);} } System.out.println(Arrays.toString(list.toArray()));這段代碼和上面的代碼只是把要刪除的元素的索引換成了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變量
next()方法的代碼如下:
("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(); }這時候你會發現這個異常是在next方法的checkForComodification中拋出的,拋出原因是modCount != expectedModCount
-
modCount是指這個list對象從new出來到現在被修改次數,當調用List的add或者remove方法的時候,這個modCount都會增加;
-
expectedModCount是Iterator類中特有的變量,指現在期望這個list被修改的次數是多少次,這個值在調用list.iterator()創建iterator的時候初始化為modCount,該值在iterator初始化直到使用結束期間不會改變。
iterator創建的時候modCount被賦值給了expectedModCount,但是調用list的add和remove方法的時候不會同時修改expectedModCount,這樣就導致下次取值時檢查到兩個count不相等,從而拋出異常。
解決這個問題的一種方式是使用Iterator來操作列表:
Iterator<String>?it?=?list.iterator(); while(it.hasNext())?{if?(it.next().equals("3"))?{it.remove();} }那么為什么這種方式不會拋出該異常呢?下面是ArrayList中內部類Itr的remove方法:
public?void?remove()?{if?(lastRet?<?0)throw?new?IllegalStateException();checkForComodification();try?{ArrayList.this.remove(lastRet);cursor?=?lastRet;lastRet?=?-1;expectedModCount?=?modCount;}?catch?(IndexOutOfBoundsException?ex)?{throw?new?ConcurrentModificationException();} }注意下面這句:
expectedModCount?=?modCount;可以看出,在使用iterator()方法得到的Iterator對象后,通過iterator.remove方法是可以正確刪除列表元素的,因為它保證了expectedModCount=modCount。
避免這個問題的另一種方法,是不使用foreach語句的for循環:
for?(int?i?=?0;?i?<?list.size();?)?{String?s?=?list.get(i);if?(s.equals("3"))?{list.remove(i);continue;}i++; }回到問題上來,在使用foreach迭代ArrayList時,是可以刪除任何一個元素的,且只能刪除一個,而且這只能發生在迭代到倒數第二個元素的時候。比如下面的代碼不會有異常:
其真正的原因是remove("5")這一句之后,下一次foreach語句將調用iterator.hasNext()方法,如果此時返回false,這樣就不會進到next()方法里了,也就不會調用checkForComodification而導致異常了。
疑問:當循環到倒數第二個元素時,如果再多刪除一個會怎樣呢?比如:
for?(String?item:?list)?{if?(item.equals("4"))?{list.remove("1");list.remove("5");}System.out.println(Arrays.toString(list.toArray())); }
這段代碼中,list是可以被打印出來的,因為list.remove()方法可以正確執行,其結果也是正確的。但是執行完這次打印,進入下一次迭代時,又產生了checkForComodification異常,還沒想明白為什么。如果哪位大牛知道,請留言。
參考:
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://stackoverflow.com/questions/11042552/what-does-the-modcount-variable-when-debugging-the-collection
from:?https://my.oschina.net/itblog/blog/422649
總結
以上是生活随笔為你收集整理的foreach迭代ArrayList时,真的不能删除元素吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA中循环删除集合中元素的方法总结
- 下一篇: List与Map的遍历过程中删除元素