面試急轉(zhuǎn)彎:List 如何一邊遍歷,一邊刪除?

這是最近面試時(shí)被問到的1道面試題,本篇博客對(duì)此問題進(jìn)行總結(jié)分享。

新手常犯的錯(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();,而該方法的核心邏輯就是比較 modCountexpectedModCount 這2個(gè)變量的值。

在上面的例子中,剛開始 modCountexpectedModCount 的值都為3,所以第1次獲取元素"博客園"是沒問題的,但是當(dāng)執(zhí)行完下面這行代碼時(shí):

platformList.remove(platform);

modCount 的值就被修改成了4。

所以在第2次獲取元素時(shí),modCountexpectedModCount 的值就不相等了,所以拋出了 java.util.ConcurrentModificationException 異常。

既然不能使用 foreach 來實(shí)現(xiàn),那么我們?cè)撊绾螌?shí)現(xiàn)呢?

主要有以下3種方法:

  • 使用 Iteratorremove() 方法

  • 使用for 循環(huán)正序遍歷

  • 使用 for 循環(huán)倒序遍歷

接下來一一講解。

使用 Iteratorremove() 方法

使用 Iteratorremove()方法的實(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 異常。

使用 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; 的用途。更多面試問題可以關(guān)注微信訂閱號(hào)碼匠筆記回復(fù)面試獲取

使用 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)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容