ArrayList 核心特性速覽
| 特性 | 說明 |
| 底層結構 | 動態數組 transient Object[] elementData |
| 初始容量 | 默認 10,new ArrayList<>(初始容量) 可指定 |
| 擴容機制 | 1.5 倍 newCapacity = old + (old >> 1),涉及 Arrays.copyOf |
| 隨機訪問 | O(1),實現 RandomAccess 標記 |
| 插入刪除 | 中間/頭部 O(n) 需搬移,尾部 O(1) 擴容均攤 |
| 線程安全 | 非線程安全,多寫需外部同步或用 CopyOnWriteArrayList |
| 允許 null | ✅ |
| 允許重複 | ✅ |
常用 API 速查
List list = new ArrayList<>();
// 添加
list.add("A"); // 尾部
list.add(0, "First"); // 指定索引
list.addAll(Arrays.asList("X","Y")); // 批量尾部
list.addAll(1, otherList); // 批量指定位置
// 查詢
String s = list.get(0); // 索引獲取
int idx = list.indexOf("A"); // 首次出現索引
int last = list.lastIndexOf("A"); // 最後出現索引
boolean has = list.contains("A"); // 是否包含
int size = list.size(); // 長度
boolean empty = list.isEmpty(); // 是否為空
// 修改
list.set(0, "New"); // 替換指定索引
// 刪除
list.remove(0); // 索引刪除
list.remove("A"); // 對象刪除 (首個匹配)
list.removeIf(s -> s.length() > 5); // 條件刪除 (Java 8+)
list.clear(); // 清空
// 遍歷
for (String s : list) {} // for-each
list.forEach(System.out::println); // Lambda
for (int i = 0; i < list.size(); i++) {} // 索引 for
// 轉換
String[] arr = list.toArray(new String[0]); // 轉數組
List copy = new ArrayList<>(list); // 拷貝
List sub = list.subList(1, 3); // 視圖 (共享底層數組)
// 排序
Collections.sort(list); // 自然序
Collections.sort(list, Comparator.reverseOrder()); // 逆序
list.sort(Comparator.naturalOrder()); // Java 8+ List.sort()
去重方案大全
方案一:HashSet 中轉 (最推薦,O(n))
// 1. 原地去重 (保留首次出現順序)
List list = Arrays.asList(2, 5, 2, 3, 2, 4);
list = new ArrayList<>(new LinkedHashSet<>(list)); // [2, 5, 3, 4]
// 2. 不保留順序 (HashSet)
list = new ArrayList<>(new HashSet<>(list)); // 順序不定
// 3. 就地修改 (避免新建對象引用變化)
Set set = new LinkedHashSet<>(list);
list.clear();
list.addAll(set);
方案二:Stream API (Java 8+,函數式)
// 保留順序
list = list.stream().distinct().collect(Collectors.toList());
// 自定義比較器去重 (如對象按 ID 去重)
list = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getId))),
ArrayList::new
));
方案三:雙重循環 (教學/無額外空間,O(n²))
// 反向遍歷避免索引錯位
for (int i = list.size() - 1; i >= 0; i--) {
for (int j = i - 1; j >= 0; j--) {
if (list.get(i).equals(list.get(j))) {
list.remove(i);
break;
}
}
}
方案四:排序後相鄰比較 (O(n log n),改變順序)
Collections.sort(list);
for (int i = list.size() - 1; i > 0; i--) {
if (list.get(i).equals(list.get(i - 1))) {
list.remove(i);
}
}
性能對比 (10 萬數據去重)
| 方案 | 耗時 | 順序 | 適用場景 |
| LinkedHashSet 中轉 | ~15ms | ✅ 保留 | 通用首選 |
| HashSet 中轉 | ~12ms | ❌ 不保留 | 不需順序 |
| Stream.distinct() | ~18ms | ✅ 保留 | 函數式管道中 |
| 排序+相鄰比較 | ~45ms | ❌ 排序序 | 已排序/可排序 |
| 雙重循環 | ~8000ms | ✅ 保留 | 極小數據/教學 |
高性能使用最佳實踐
- 預估容量:
new ArrayList<>(expectedSize) 避免多次擴容拷貝
- 批量添加用 addAll:單次擴容、單次拷貝
- 頻繁頭部操作用 LinkedList/ArrayDeque
- 多線程寫入用 CopyOnWriteArrayList:讀多寫少、遍歷無需加鎖
- 只讀場景用 Collections.unmodifiableList / List.of (JDK 9+)
- 避免在循環中 remove/contains:
O(n²),改用 Iterator.remove() 或 removeIf()
- subList 視圖謹慎用:底層共享數組,結構修改會失效,僅用於臨時只讀切片
- 並行流處理大數據:
list.parallelStream()... (需隨機訪問支持)
常見陷阱與避坑
| 代碼 | 問題 | 修正 |
list.remove(i) 正向循環 | 跳過元素/索引越界 | 反向循環 for(int i=size-1;i>=0;i--) 或 Iterator.remove() |
list.contains() 在循環中 | O(n²) 性能災難 | 用 HashSet 輔助或 removeIf() |
Arrays.asList(arr).add() | 拋 UnsupportedOperationException (定長列表) | new ArrayList<>(Arrays.asList(arr)) |
List list = new ArrayList(); (無泛型) | 原始類型,編譯警告、運行時 ClassCastException | 始終指定泛型 new ArrayList<>() |
| 序列化含 null 元素 | 部分框架反序列化異常 | 業務層過濾 null 或配置序列化策略 |
ArrayList vs LinkedList vs Vector 選型
| 場景 | 首選 | 備選 |
| 隨機訪問、末尾增刪、讀多寫少 | ArrayList | Vector (需同步) |
| 頻繁首尾增刪、隊列/棧、迭代器操作 | ArrayDeque / LinkedList | |
| 多線程並發寫入、遍歷無鎖 | CopyOnWriteArrayList | Collections.synchronizedList |
| 只讀、不可變 | List.of() / Collections.unmodifiableList() | |
總結
ArrayList 是 Java 最常用的列表實現,動態數組 + 隨機訪問 O(1) + 尾部增刪 O(1) 適合絕大多數業務場景。核心技巧:預估容量、LinkedHashSet 去重保序、removeIf 條件刪除、批量操作、並行流。理解擴容機制、線程不安全、subList 視圖特性,避開常見陷阱,能寫出高性能、無 Bug 的列表操作代碼。