foreach迭代ArrayList时,真的不能删除元素吗?
ArrayList是java開發(fā)時非常常用的類,常碰到需要對ArrayList循環(huán)刪除元素的情況。這時候大家都不會使用foreach循環(huán)的方式來遍歷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循環(huán)時刪除元素一定會拋這個異常呢?答案是否定的。
見這個代碼:
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都試一遍,發(fā)現(xiàn),除了刪除4之外,刪除其他元素都會拋異常。接著把list的元素個數(shù)增加到7試試,這時候可以發(fā)現(xiàn)規(guī)律是,只有刪除倒數(shù)第二個元素的時候不會拋出異常,刪除其他元素都會拋出異常。
好吧,規(guī)律知道了,可以從代碼的角度來揭開謎底了。
首先java的foreach循環(huán)其實就是根據(jù)list對象創(chuàng)建一個Iterator迭代對象,用這個迭代對象來遍歷list,相當(dāng)于list對象中元素的遍歷托管給了Iterator,你如果要對list進行增刪操作,都必須經(jīng)過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(); }這時候你會發(fā)現(xiàn)這個異常是在next方法的checkForComodification中拋出的,拋出原因是modCount != expectedModCount
-
modCount是指這個list對象從new出來到現(xiàn)在被修改次數(shù),當(dāng)調(diào)用List的add或者remove方法的時候,這個modCount都會增加;
-
expectedModCount是Iterator類中特有的變量,指現(xiàn)在期望這個list被修改的次數(shù)是多少次,這個值在調(diào)用list.iterator()創(chuàng)建iterator的時候初始化為modCount,該值在iterator初始化直到使用結(jié)束期間不會改變。
iterator創(chuàng)建的時候modCount被賦值給了expectedModCount,但是調(diào)用list的add和remove方法的時候不會同時修改expectedModCount,這樣就導(dǎo)致下次取值時檢查到兩個count不相等,從而拋出異常。
解決這個問題的一種方式是使用Iterator來操作列表:
Iterator<String>?it?=?list.iterator(); while(it.hasNext())?{if?(it.next().equals("3"))?{it.remove();} }那么為什么這種方式不會拋出該異常呢?下面是ArrayList中內(nèi)部類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循環(huán):
for?(int?i?=?0;?i?<?list.size();?)?{String?s?=?list.get(i);if?(s.equals("3"))?{list.remove(i);continue;}i++; }回到問題上來,在使用foreach迭代ArrayList時,是可以刪除任何一個元素的,且只能刪除一個,而且這只能發(fā)生在迭代到倒數(shù)第二個元素的時候。比如下面的代碼不會有異常:
其真正的原因是remove("5")這一句之后,下一次foreach語句將調(diào)用iterator.hasNext()方法,如果此時返回false,這樣就不會進到next()方法里了,也就不會調(diào)用checkForComodification而導(dǎo)致異常了。
疑問:當(dāng)循環(huán)到倒數(shù)第二個元素時,如果再多刪除一個會怎樣呢?比如:
for?(String?item:?list)?{if?(item.equals("4"))?{list.remove("1");list.remove("5");}System.out.println(Arrays.toString(list.toArray())); }
這段代碼中,list是可以被打印出來的,因為list.remove()方法可以正確執(zhí)行,其結(jié)果也是正確的。但是執(zhí)行完這次打印,進入下一次迭代時,又產(chǎn)生了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
總結(jié)
以上是生活随笔為你收集整理的foreach迭代ArrayList时,真的不能删除元素吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA中循环删除集合中元素的方法总结
- 下一篇: List与Map的遍历过程中删除元素