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

這是最近面試時被問到的1道面試題,本篇博客對此問題進行總結分享。

1. 新手常犯的錯誤

可能很多新手(包括當年的我,哈哈)第一時間想到的寫法是下面這樣的:

<code>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);
}
/<code>

然後滿懷信心的去運行,結果竟然拋
java.util.ConcurrentModificationException異常了,翻譯成中文就是:併發修改異常。

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

是不是很懵,心想這是為什麼呢?

讓我們首先看下上面這段代碼生成的字節碼,如下所示:

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

由此可以看出,foreach循環在實際執行時,其實使用的是Iterator,使用的核心方法是hasnext()和next()。

然後再來看下ArrayList類的Iterator是如何實現的呢?

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

可以看出,調用next()方法獲取下一個元素時,第一行代碼就是調用了checkForComodification();,而該方法的核心邏輯就是比較modCount和expectedModCount這2個變量的值。

在上面的例子中,剛開始modCount和expectedModCount的值都為3,所以第1次獲取元素"博客園"是沒問題的,但是當執行完下面這行代碼時:

<code>platformList.remove(platform);/<code>

modCount的值就被修改成了4。

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

所以在第2次獲取元素時,modCount和expectedModCount的值就不相等了,所以拋出了
java.util.ConcurrentModificationException異常。

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

既然不能使用foreach來實現,那麼我們該如何實現呢?

主要有以下3種方法:

使用Iterator的remove()方法

使用for循環正序遍歷

使用for循環倒序遍歷

接下來一一講解。

2. 使用Iterator的remove()方法

使用Iterator的remove()方法的實現方式如下所示:

<code>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);
}
/<code>

輸出結果為:

[CSDN, 掘金]

為什麼使用iterator.remove();就可以呢?

讓我們看下它的源碼:

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

可以看出,每次刪除一個元素,都會將modCount的值重新賦值給expectedModCount,這樣2個變量就相等了,不會觸發
java.util.ConcurrentModificationException異常。

3. 使用for循環正序遍歷

使用for循環正序遍歷的實現方式如下所示:

<code>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);
}/<code>


這種實現方式比較好理解,就是通過數組的下標來刪除,不過有個注意事項就是刪除元素後,要修正下下標的值:

<code>i = i - 1;/<code>


為什麼要修正下標的值呢?

因為剛開始元素的下標是這樣的:

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

第1次循環將元素"博客園"刪除後,元素的下標變成了下面這樣:

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

第2次循環時i的值為1,也就是取到了元素”掘金“,這樣就導致元素"CSDN"被跳過檢查了,所以刪除完元素後,我們要修正下下標,這也是上面代碼中i = i - 1;的用途。

4. 使用for循環倒序遍歷

使用for循環倒序遍歷的實現方式如下所示:

<code>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);
}
/<code>

這種實現方式和使用for循環正序遍歷類似,不過不用再修正下標,因為剛開始元素的下標是這樣的:

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

第1次循環將元素"掘金"刪除後,元素的下標變成了下面這樣:

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

第2次循環時i的值為1,也就是取到了元素”CSDN“,不會導致跳過元素,所以不需要修正下標。


分享到:


相關文章: