面試官:刪除List的姿勢知道幾種?

前言

  • 前段時間在面試的實習生的時候,問了這樣一個相當基礎的問題:ArrayList如何一邊遍歷,一邊刪除? 實習生支支吾吾的答不上來,說自己也沒經常用到。

  • 爲免更多入門小白被難倒,陳某今天帶大家聊聊這個問題。

小白的姿勢

  • 很多小白的代碼萬變不離其中,如下:

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);
}
  • 然後滿懷信心的去運行,結果竟然拋java.util.ConcurrentModificationException異常了,翻譯成中文就是:併發修改異常。

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

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

  • 由此可以看出,foreach循環在實際執行時,其實使用的是Iterator,使用的核心方法是hasnext()next()。然後再來看下ArrayList類的Iterator是如何實現的呢?

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

  • 在上面的例子中,剛開始modCountexpectedModCount的值都爲3,所以第1次獲取元素"博客園"是沒問題的,但是當執行完下面這行代碼時modCount的值就被修改成了4。

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

老司機的姿勢

  • 既然不能使用foreach來實現,那麼該如何實現呢?其實大多數場景下無非有如下的幾種方法。

使用iterator的remove方法

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

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);
}
  • 爲什麼使用iterator.remove();就可以呢?讓我們來看看源碼:

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

使用for循環正序遍歷

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

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);
}
  • 這種實現方式比較好理解,就是通過數組的下標來刪除,不過有個注意事項就是刪除元素後,要修正下下標的值:

i = i - 1;

使用for循環倒序遍歷

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

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);
}
  • 這種實現方式和使用for循環正序遍歷類似,不過不用再修正下標,因爲剛開始元素的下標是這樣的:

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

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

總結

  • 看似簡單的面試題,其實考察的你對ArrayList內部存儲結構和源碼的瞭解程度,如果對你有所幫助,歡迎轉發在看,謝謝支持!!!

往期推薦

萬惡的NPE如何避免,幾種你必須知道的方案!!!面試官:消息隊列這些我必問!一文搞定分佈式系統ID生成方案這是我看過關於 volatile 最好的文章

幹掉可惡的  "try catch "!

看完這篇接口限流,又能和麪試官扯皮了~

Spring boot高頻面試題及答案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章