1. 程式人生 > >【Java面試題】List如何一邊遍歷,一邊刪除?

【Java面試題】List如何一邊遍歷,一邊刪除?

這是最近面試時被問到的1道面試題,本篇部落格對此問題進行總結分享。 ## 1. 新手常犯的錯誤 可能很多新手(包括當年的我,哈哈)第一時間想到的寫法是下面這樣的: ```java public static void main(String[] args) { List platformList = new ArrayList<>(); platformList.add("部落格園"); platformList.add("CSDN"); platformList.add("掘金"); for (String platform : platformList) { if (platform.equals("部落格園")) { platformList.remove(platform); } } System.out.println(platformList); } ``` 然後滿懷信心的去執行,結果竟然拋`java.util.ConcurrentModificationException`異常了,翻譯成中文就是:併發修改異常。 ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_104501.png) 是不是很懵,心想這是為什麼呢? 讓我們首先看下上面這段程式碼生成的位元組碼,如下所示: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_104925.png) 由此可以看出,foreach迴圈在實際執行時,其實使用的是`Iterator`,使用的核心方法是`hasnext()`和`next()`。 然後再來看下ArrayList類的Iterator是如何實現的呢? ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_110039.png) 可以看出,呼叫`next()`方法獲取下一個元素時,第一行程式碼就是呼叫了`checkForComodification();`,而該方法的核心邏輯就是比較`modCount`和`expectedModCount`這2個變數的值。 在上面的例子中,剛開始`modCount`和`expectedModCount`的值都為3,所以第1次獲取元素"部落格園"是沒問題的,但是當執行完下面這行程式碼時: ```java platformList.remove(platform); ``` `modCount`的值就被修改成了4。 ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_111204.png) 所以在第2次獲取元素時,`modCount`和`expectedModCount`的值就不相等了,所以丟擲了`java.util.ConcurrentModificationException`異常。 ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_111615.png) 既然不能使用foreach來實現,那麼我們該如何實現呢? 主要有以下3種方法: 1. 使用Iterator的remove()方法 2. 使用for迴圈正序遍歷 3. 使用for迴圈倒序遍歷 接下來一一講解。 ## 2. 使用Iterator的remove()方法 使用Iterator的remove()方法的實現方式如下所示: ```java public static void main(String[] args) { List platformList = new ArrayList<>(); platformList.add("部落格園"); platformList.add("CSDN"); platformList.add("掘金"); Iterator iterator = platformList.iterator(); while (iterator.hasNext()) { String platform = iterator.next(); if (platform.equals("部落格園")) { iterator.remove(); } } System.out.println(platformList); } ``` 輸出結果為: > [CSDN, 掘金] 為什麼使用`iterator.remove();`就可以呢? 讓我們看下它的原始碼: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_112736.png) 可以看出,每次刪除一個元素,都會將`modCount`的值重新賦值給`expectedModCount`,這樣2個變數就相等了,不會觸發`java.util.ConcurrentModificationException`異常。 ## 3. 使用for迴圈正序遍歷 使用for迴圈正序遍歷的實現方式如下所示: ```java public static void main(String[] args) { List 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); } ``` 這種實現方式比較好理解,就是通過陣列的下標來刪除,不過有個注意事項就是刪除元素後,要修正下下標的值: ```java i = i - 1; ``` 為什麼要修正下標的值呢? 因為剛開始元素的下標是這樣的: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_114435.png) 第1次迴圈將元素"部落格園"刪除後,元素的下標變成了下面這樣: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_114736.png) 第2次迴圈時i的值為1,也就是取到了元素”掘金“,這樣就導致元素"CSDN"被跳過檢查了,所以刪除完元素後,我們要修正下下標,這也是上面程式碼中`i = i - 1;`的用途。 ## 4. 使用for迴圈倒序遍歷 使用for迴圈倒序遍歷的實現方式如下所示: ```java public static void main(String[] args) { List 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); } ``` 這種實現方式和使用for迴圈正序遍歷類似,不過不用再修正下標,因為剛開始元素的下標是這樣的: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_114435.png) 第1次迴圈將元素"掘金"刪除後,元素的下標變成了下面這樣: ![](https://images.zwwhnly.com/picture/2020/03/snipaste_20200320_115710.png) 第2次迴圈時i的值為1,也就是取到了元素”CSDN“,不會導致跳過元素,所以不需要修正下標。 ## 5. 參考 [Java集合怎麼一邊刪除一邊遍歷](https://blog.csdn.net/zjwcdd/article/details/51513879) [java 為什麼遍歷的時候不能刪除元素](https://blog.csdn.net/wangjun5159/article/details/6