本文整理自《編寫高質量代碼:改善 Java 程序的 151 個建議》第 5 章,深入剖析陣列與集合使用中的常見陷阱與最佳實踐,包含原始型別陣列轉換、asList 不可變性、List 遍歷效能、LinkedList 與 ArrayList 選型、集合相等性判斷等核心議題。
int[] data = {1, 2, 3, 4, 5};
List list = Arrays.asList(data); // 陷阱!
System.out.println(list.size()); // 輸出 1,非 5
原因:Arrays.asList(T... a) 接收泛型可變參數,基本型別無法泛型化。int[] 被視為單一元素(型別為 int[]),而非 5 個 Integer。
解決:使用包裝類型 Integer[] 替代 int[]。
注意:8 種基本型別陣列均存在此問題,轉 List 時必須使用包裝類型陣列。
List<String> names = Arrays.asList("張三", "李四", "王五");
names.add("趙六"); // 拋出 UnsupportedOperationException
原因:Arrays.asList 回傳的是 Arrays 內部類 ArrayList(非 java.util.ArrayList),未覆寫 add/remove 方法,父類 AbstractList 預設拋出異常。
對策:
asListnew ArrayList<>(Arrays.asList(...))核心原則:實作 RandomAccess 介面(如 ArrayList)用索引遍歷;未實作(如 LinkedList)用迭代器/foreach 遍歷。
public static int average(List<Integer> scores) {
int sum = 0;
if (scores instanceof RandomAccess) {
// 隨機存取:索引遍歷更快
for (int i = 0; i < scores.size(); i++) {
sum += scores.get(i);
}
} else {
// 循序存取:迭代器遍歷更快
for (Integer score : scores) {
sum += score;
}
}
return sum / scores.size();
}
效能實測(80 萬筆資料):
ArrayList:索引遍歷 8ms vs foreach 11msLinkedList:foreach 12ms vs 索引遍歷極慢(每次 get 需從頭/尾遍歷)| 操作 | ArrayList | LinkedList | 勝出 |
|---|---|---|---|
| 插入 | 需陣列拷貝位移 O(n) | 僅修改指標 O(1) | LinkedList |
| 刪除 | 需陣列拷貝位移 O(n) | 僅修改指標 O(1) | LinkedList |
| 修改 | 直接索引賦值 O(1) | 需遍歷定位 O(n) | ArrayList |
| 隨機存取 | O(1) | O(n) | ArrayList |
實務建議:寫多讀少、頻繁頭尾操作選 LinkedList;讀多寫少、需隨機存取選 ArrayList(預設首選)。
ArrayList<String> list1 = new ArrayList<>();
list1.add("A");
Vector<String> list2 = new Vector<>();
list2.add("A");
System.out.println(list1.equals(list2)); // true
原理:AbstractList.equals() 僅檢查:
List 介面實作equals 相等完全不關心具體實作類別(ArrayList、Vector、LinkedList 等)。Set、Map 同理。
這五個建議揭示了 Java 集合框架設計的核心哲學:介面抽象、效能權衡、防禦性程式設計。深入理解底層實作(陣列 vs 鏈表、隨機存取 vs 循序存取、淺拷貝 vs 深拷貝),才能在實戰中做出正確選擇,避開隱藏陷阱,寫出高效、健壯的程式碼。