【Java面試題】List如何一邊遍歷,一邊刪除?
阿新 • • 發佈:2020-03-20
這是最近面試時被問到的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