自 Java 5 (2004) 引入 for-each 循環 (增強 for 循環) 以來,它已成為遍歷集合、陣列的標準寫法。但傳統 for (int i=0; i<len; i++) 仍有不可替代的場景。本文從字節碼、語義、效能、適用場景四個維度深度對比,給出明確選擇指南。
| 維度 | for-each | 傳統 for |
|---|---|---|
| 語法 | for (Type e : collection) | for (int i=0; i<arr.length; i++) |
| 適用對象 | 陣列、實作 Iterable 的集合 | 陣列、List (隨機訪問)、任何可索引結構 |
| 索引訪問 | 無 (僅元素值) | 有 (索引 i) |
| 元素修改 | 不可修改集合結構 (增刪) | 可修改結構 (需小心索引偏移) |
| 並行流轉換 | .parallelStream() 易轉換 | 需手動分割範圍 |
// 源碼
for (String s : arr) { System.out.println(s); }
// 編譯器生成 (等價傳統 for)
for (int i = 0; i < arr.length; i++) {
String s = arr[i];
System.out.println(s);
}
// 字節碼幾乎完全一致,無額外開銷
// 源碼
for (String s : list) { System.out.println(s); }
// 編譯器生成 (使用 Iterator)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
// 額外分配 Iterator 物件 (極微小,通常逃逸分析優化掉)
| 場景 | for-each | 傳統 for | 結論 |
|---|---|---|---|
陣列 int[] 順序訪問 | ~1.0x | ~1.0x | **持平**,JIT 完全優化為相同機器碼 |
ArrayList 順序訪問 | ~1.0x | ~1.0x (get(i)) | **持平**,Iterator 內部同樣是索引訪問 |
LinkedList 順序訪問 | ~1.0x | ~100x+ (get(i) 為 O(n)) | **for-each 完勝**,Iterator 為 O(1) 移動指針 |
| 反向遍歷 | 不支援 | 支援 | 傳統 for 必選 |
| 需索引運算 | 不支援 | 支援 | 傳統 for 必選 |
需要索引 / 反向遍歷 / 修改集合結構 (增刪元素)?
├─ 是 → 傳統 for (或 ListIterator)
└─ 否 → 僅順序讀取元素值?
├─ 是 → **for-each (首選)**:語義清晰、防越界、支援所有 Iterable、可無縫遷移並行流
└─ 否 → 特殊需求 (步長、跳過、多集合同步遍歷) → 傳統 for 或 索引ベース迭代器
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
for (String s : list) {
if ("b".equals(s)) list.remove(s); // 拋出 CME!
}
// 修正:使用 Iterator 顯式 remove
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove(); // 安全
}
// 或 Java 8+ removeIf
list.removeIf("b"::equals);
for (String s : list) { /* s 僅在循環體內可見 */ }
String s = "outer"; // 編譯錯誤:變量 s 已在外層作用域宣告
// for-each 循環變量隱含在循環體塊內,不洩露到外部
int[] arr = {1,2,3};
for (int x : arr) { x = 10; } // 修改的是副本 x,arr 不變
// 修正:傳統 for 修改 arr[i]
for (int i=0; i
現代替代方案:Stream API (Java 8+)
// 僅讀取/轉換/過濾/聚合 → Stream 更聲明式
list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
// 並行處理 → 僅改 .parallelStream()
list.parallelStream().map(String::toUpperCase).collect(Collectors.toList());
// 收集結果 → Collector 豐富組合
Map<Boolean, List<String>> partitioned = list.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 5));
選擇建議:簡單副作用循環 (打印、累加) 用 for-each;複雜數據管道 (過濾、映射、聚合、分組) 用 Stream;需索引/高性能數值計算用傳統 for 或 primitive Stream (IntStream)。
總結
- for-each 是 90%+ 遍歷場景的最佳選擇:語法簡潔、語義明確、防越界、支援所有
Iterable、無效能損耗。
- 傳統 for 保留 3 大核心優勢:索引訪問、反向遍歷、修改集合結構/基本類型陣列元素。
- 效能無差異 (陣列/ArrayList),LinkedList 必須用 for-each/Iterator。
- 現代代碼風格:優先 for-each → 複雜邏輯遷移 Stream → 極致性能/索引需求用傳統 for。
掌握這三種遍歷方式的邊界與優勢,寫出的代碼既符合現代 Java 風格,又能在性能敏感路徑做出正確抉擇。