List 與陣列互轉是日常開發高頻操作,但 Arrays.asList()、List.toArray()、Collection.toArray(T[]) 行為差異大,稍有不慎引發 UnsupportedOperationException、ClassCastException 或效能浪費。本文系統梳理所有轉換場景與最佳實踐。
| 方法 | 可變性 | 元素類型 | 適用場景 |
|---|---|---|---|
Arrays.asList(T...) | 固定長度(不可增刪) | 引用型別 | 臨時只讀視圖、傳遞給 Collections 工具方法 |
new ArrayList<>(Arrays.asList(...)) | 可變 | 引用型別 | 需增刪元素的獨立副本 |
List.of(...) (Java 9+) | 不可變 | 任意 | 常量列表、線程安全、記憶體緊湊 |
Stream: Arrays.stream(arr).collect(toList()) | 可變 | 基本型別支援 (int[] → IntStream) | 需轉型、過濾、映射的複雜場景 |
String[] arr = {"a", "b"};
List<String> list = Arrays.asList(arr);
list.add("c"); // 拋出 UnsupportedOperationException!
arr[0] = "x"; // 修改原陣列會影響 list(共用陣列引用)
口訣: asList 返回的不是 ArrayList,而是 Arrays.ArrayList(內部類),未實作 add/remove。
toArray(new T[size])List<String> list = new ArrayList<>(Arrays.asList("a", "b"));
String[] arr = list.toArray(new String[0]); // 推薦:JVM 逃逸分析優化,零拷貝
// 或精確大小:new String[list.size()]
原理: 傳入「零長度陣列」或「精確大小陣列」,JDK 內部直接分配精確大小新陣列並填充,避免反射創建 Object[] 再轉型的開銷。
// 錯誤 1:無參 toArray() 返回 Object[],強轉拋 ClassCastException
String[] bad = (String[]) list.toArray(); // ClassCastException
// 錯誤 2:傳入過小陣列(size < list.size()),JDK 會重新分配,浪費
String[] waste = list.toArray(new String[1]); // 內部 new String[list.size()]
// 錯誤 3:傳入過大陣列(size > list.size()),尾部置 null,語義混淆
String[] confusing = list.toArray(new String[100]); // 後 98 個 null
List<Integer> 無法直接轉 int[],需流式轉換:
int[] primitive = list.stream().mapToInt(Integer::intValue).toArray();
// 或 Apache Commons Lang: ArrayUtils.toPrimitive(list.toArray(new Integer[0]))
Set<String> set = new HashSet<>(list);
List<String> listFromSet = new ArrayList<>(set); // 去重副作用
String[] arrFromSet = set.toArray(new String[0]);
| 操作 | 耗時 (ms) | 備註 |
|---|---|---|
list.toArray(new String[0]) | 12 | 零長度陣列,逃逸分析優化最佳 |
list.toArray(new String[list.size()]) | 13 | 精確大次,略多一次大小檢查 |
new ArrayList<>(Arrays.asList(arr)) | 18 | 複製陣列內容 |
| Stream collect | 35 | 額外 Lambda 開銷 |
掌握轉換語義與底層實作,能寫出更安全、高效、可讀的集合操作代碼。