iT邦幫忙

2026 iThome 鐵人賽

0
Build on Google AI

把Google AI當夥伴打造個人高效Coding工作流系列 第 16 篇

第十六天Collections工具類防禦陷阱、Arrays.asLis隱患與Java 9 List.of不可變集合

  • 分享至 

  • xImage
  •  

一、 Arrays.asList() 的三大架構隱患
在 Java 8 以前,開發者最常使用 Arrays.asList(...) 將多個元素或陣列轉換為 List,但其底層封裝帶來了極易踩雷的副作用:

  1. 偽裝的 ArrayList(拋出 UnsupportedOperationException)
    陷阱本質:Arrays.asList() 返回的並非 java.util.ArrayList,而是 java.util.Arrays 內部定義的一個靜態私有內部類別(Private Static Class)。

行為限制:該內部類別繼承自 AbstractList,卻未覆寫 add()、remove()、clear() 等結構性變更方法。

執行崩潰:若對其呼叫 list.add("test"),執行期直接噴出 UnsupportedOperationException。

  1. 底層陣列與 List 的「雙向共用(Backing Array)」
    Arrays.asList() 並沒有拷貝資料,而是直接拿傳入的陣列作為內部儲存引用。

改陣列動 List:修改原陣列的元素值,list.get() 取得的值會跟著改變。

改 List 動陣列:對 List 呼叫允許的 list.set(0, "newVal"),原陣列對應位置的數值也會被同步篡改,破壞封裝性。

  1. 基本型別陣列轉型的「單元素陷阱」
    傳入物件陣列(如 String[] 或 Integer[])時,會如預期將陣列元素展開為 List 元素。

傳入基本型別陣列(如 int[] arr = {1, 2, 3};):

因泛型型別擦除無法支援基本型別,JVM 會將整個 int[] 陣列當作單一物件。

Arrays.asList(arr) 回傳的實際型態為 List<int[]>,長度 list.size() 是 1 而不是 3。


二、 Collections 工具類的防禦性包裝與假不可變

  1. Collections.unmodifiableList(originalList) 的破綻
    實作原理:利用裝飾者模式(Decorator Pattern),把原始 List 包在一層只讀 Wrapper 中,攔截所有寫入方法並拋出例外。

「非真正不可變(Not Truly Immutable)」的隱憂:

Wrapper 內部直接指向外部傳入的 originalList 引用。

若呼叫端依然保留對 originalList 的引用並執行 originalList.add("evil"),這個唯讀 Wrapper 取得的內容依然會同步被更新。

結論:它只是「不可直接透過此視圖修改(Unmodifiable View)」,並非記憶體層面真正的不可變集合(Immutable Collection)。

  1. Collections.synchronizedList() 的併發瓶頸
    透過粗暴的物件鎖(synchronized (mutex))將 List 的每個方法包裹保護。

迭代器(Iterator)未保護地雷:即使使用了 synchronizedList,在執行 for-each 或遍歷時,迭代過程並未被自動加鎖,多執行緒同時寫入時依然會拋出 ConcurrentModificationException,必須由開發者手動在外部寫 synchronized (list) { ... } 進行雙重鎖定。


三、 現代 Java 9+ 原生不可變集合:List.of() / Set.of() / Map.of()
Java 9 引入的工廠方法徹底解決了過去十幾年集合初始化的痛點。

  1. 真正的不可變性(True Immutability)
    底層不再引用任何外部陣列,而是在內部建立完全獨立、長度固定的精簡結構。

任何呼叫 add()、set()、remove() 的操作,一律在編譯與執行期拋出 UnsupportedOperationException。

不存在背後被原始陣列或原始集合修改的副作用。

  1. 嚴格的 Null 禁令(Fail-Fast NPE)
    Arrays.asList() 允許放入 null。

List.of()、Set.of()、Map.of() 嚴禁包含任何 null 元素 / Key / Value:
List.of("A", null, "C"); // 建立時立即拋出 NullPointerException
架構價值:強迫開發者在資料進入集合的第一時間完成非空校驗,徹底消滅日後延遲噴出 NPE 的隱蔽臭蟲。

  1. 記憶體極限最佳化(Compact Storage)
    new ArrayList<>() 預設至少配置 10 個元素的陣列空間與相關指標變數。

List.of() 在底層針對長度進行了特化實作:

長度為 0:使用全域共用單例 ImmutableCollections.EMPTY_LIST。

長度為 1 或 2:分別有專屬的 List12 實作,內部直接用欄位儲存元素,連底層陣列物件都不用建,極致節省 Heap 記憶體並提升存取速度。

  1. Set.of() 的隨機化雜湊迭代順序(Hash Randomization)
    傳統 HashSet 的迭代順序雖然未保證,但在相同環境下通常相對固定,導致很多初學者依賴其隱晦的順序。

Set.of() 在每次 JVM 啟動時都會注入隨機鹽值(Salt),故意在不同次執行時給出不同的遍歷順序,從語法層面強迫工程師不得寫出依賴順序的脆弱程式碼。


上一篇
第十五天LinkedHashMap的LRU快取機制與TreeMap紅黑樹排序原理
下一篇
第十七天Thread生命週期六大狀態切換、synchronized底層Monitor機制與死鎖預防
系列文
把Google AI當夥伴打造個人高效Coding工作流 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言