Java集合—List如何一边遍历,一边删除?
原文作者:申城異鄉(xiāng)人
原文地址:【Java面試題】List如何一邊遍歷,一邊刪除?
目錄
1. 新手常犯的錯(cuò)誤
2. 使用Iterator的remove()方法
3. 使用for循環(huán)正序遍歷
4. 使用for循環(huán)倒序遍歷
5.1 使用removeIf()方法(推薦)
5.2 使用for循環(huán)正序遍歷,是否需要修正下標(biāo)?
這是最近面試時(shí)被問到的1道面試題,本篇博客對此問題進(jìn)行總結(jié)分享。
1. 新手常犯的錯(cuò)誤
可能很多新手(包括當(dāng)年的我,哈哈)第一時(shí)間想到的寫法是下面這樣的:
public static void main(String[] args) {List<String> platformList = new ArrayList<>();platformList.add("博客園");platformList.add("CSDN");platformList.add("掘金");for (String platform : platformList) {if (platform.equals("博客園")) {platformList.remove(platform);}}System.out.println(platformList); }然后滿懷信心的去運(yùn)行,結(jié)果竟然拋java.util.ConcurrentModificationException異常了,翻譯成中文就是:并發(fā)修改異常。
?
是不是很懵,心想這是為什么呢?讓我們首先看下上面這段代碼生成的字節(jié)碼,如下所示:
?
由此可以看出,foreach循環(huán)在實(shí)際執(zhí)行時(shí),其實(shí)使用的是Iterator,使用的核心方法是hasnext()和next()。然后再來看下ArrayList類的Iterator是如何實(shí)現(xiàn)的呢?
?
可以看出,調(diào)用next()方法獲取下一個(gè)元素時(shí),第一行代碼就是調(diào)用了checkForComodification();,而該方法的核心邏輯就是比較modCount和expectedModCount這2個(gè)變量的值。在上面的例子中,剛開始modCount和expectedModCount的值都為3,所以第1次獲取元素"博客園"是沒問題的,但是當(dāng)執(zhí)行完下面這行代碼時(shí):
platformList.remove(platform);modCount的值就被修改成了4。
?
所以在第2次獲取元素時(shí),modCount和expectedModCount的值就不相等了,所以拋出了java.util.ConcurrentModificationException異常。
?
既然不能使用foreach來實(shí)現(xiàn),那么我們該如何實(shí)現(xiàn)呢?主要有以下3種方法:
2. 使用Iterator的remove()方法
使用Iterator的remove()方法的實(shí)現(xiàn)方式如下所示:
public static void main(String[] args) {List<String> platformList = new ArrayList<>();platformList.add("博客園");platformList.add("CSDN");platformList.add("掘金");Iterator<String> iterator = platformList.iterator();while (iterator.hasNext()) {String platform = iterator.next();if (platform.equals("博客園")) {iterator.remove();}}System.out.println(platformList); }輸出結(jié)果為:
[CSDN, 掘金]
為什么使用iterator.remove();就可以呢?讓我們看下它的源碼:
?
可以看出,每次刪除一個(gè)元素,都會(huì)將modCount的值重新賦值給expectedModCount,這樣2個(gè)變量就相等了,不會(huì)觸發(fā)java.util.ConcurrentModificationException異常。
3. 使用for循環(huán)正序遍歷
使用for循環(huán)正序遍歷的實(shí)現(xiàn)方式如下所示:
public static void main(String[] args) {List<String> platformList = new ArrayList<>();platformList.add("博客園");platformList.add("CSDN");platformList.add("掘金");for (int i = 0; i < platformList.size(); i++) {String item = platformList.get(i);if (item.equals("博客園")) {platformList.remove(i);i = i - 1;}}System.out.println(platformList); }這種實(shí)現(xiàn)方式比較好理解,就是通過數(shù)組的下標(biāo)來刪除,不過有個(gè)注意事項(xiàng)就是刪除元素后,要修正下下標(biāo)的值:
i = i - 1;為什么要修正下標(biāo)的值呢?因?yàn)閯傞_始元素的下標(biāo)是這樣的:
?
第1次循環(huán)將元素"博客園"刪除后,元素的下標(biāo)變成了下面這樣:
?
第2次循環(huán)時(shí)i的值為1,也就是取到了元素”掘金“,這樣就導(dǎo)致元素"CSDN"被跳過檢查了,所以刪除完元素后,我們要修正下下標(biāo),這也是上面代碼中i = i - 1;的用途。
4. 使用for循環(huán)倒序遍歷
使用for循環(huán)倒序遍歷的實(shí)現(xiàn)方式如下所示:
public static void main(String[] args) {List<String> platformList = new ArrayList<>();platformList.add("博客園");platformList.add("CSDN");platformList.add("掘金");for (int i = platformList.size() - 1; i >= 0; i--) {String item = platformList.get(i);if (item.equals("掘金")) {platformList.remove(i);}}System.out.println(platformList); }這種實(shí)現(xiàn)方式和使用for循環(huán)正序遍歷類似,不過不用再修正下標(biāo),因?yàn)閯傞_始元素的下標(biāo)是這樣的:
?
第1次循環(huán)將元素"掘金"刪除后,元素的下標(biāo)變成了下面這樣:
?
第2次循環(huán)時(shí)i的值為1,也就是取到了元素”CSDN“,不會(huì)導(dǎo)致跳過元素,所以不需要修正下標(biāo)。
5.1 使用removeIf()方法(推薦)
從JDK1.8開始,可以使用removeIf()方法來代替?Iterator的remove()方法實(shí)現(xiàn)一邊遍歷一邊刪除,其實(shí),IDEA中也會(huì)提示:
?
所以原來的代碼:
Iterator<String> iterator = platformList.iterator(); while (iterator.hasNext()) {String platform = iterator.next();if (platform.equals("博客園")) {iterator.remove();} }就可以簡化為如下所示的1行代碼,非常簡潔:
platformList.removeIf(platform -> "博客園".equals(platform));看下removeIf()方法的源碼,會(huì)發(fā)現(xiàn)其實(shí)底層也是用的Iterator的remove()方法:
?
5.2 使用for循環(huán)正序遍歷,是否需要修正下標(biāo)?
先說結(jié)論:需要。不過之前文中舉得例子不是太好,所以好多讀者看完認(rèn)為不修正下標(biāo)也是可以的,其實(shí)不是,我們換個(gè)例子來理解:
List<String> platformList = new ArrayList<>(); platformList.add("博客園"); platformList.add("博客園"); platformList.add("CSDN"); platformList.add("掘金");for (int i = 0; i < platformList.size(); i++) {String item = platformList.get(i);if ("博客園".equals(item)) {platformList.remove(i);} }System.out.println(platformList);輸出結(jié)果:[博客園, CSDN, 掘金]
可以發(fā)現(xiàn),如果不修正下標(biāo),第2個(gè)元素“博客園”在循環(huán)遍歷時(shí)被跳過了,也就無法刪除,所以一定要修正下標(biāo):
?
總結(jié)
以上是生活随笔為你收集整理的Java集合—List如何一边遍历,一边删除?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Java集合—HashMap为什么2倍扩
 - 下一篇: Java并发编程—说说Runnable与