首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 遍歷集合/陣列:for-each vs 傳統 for 循環的效能、語義與最佳選擇

     編輯:Java 相關知識

自 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() 易轉換需手動分割範圍

字節碼層面真相 (JDK 17 編譯)

陣列遍歷

// 源碼
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 物件 (極微小,通常逃逸分析優化掉)

效能實測 (JMH 基準測試參考)

場景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 或 索引ベース迭代器

常見陷阱與避坑

陷阱 1:for-each 中修改集合結構 → ConcurrentModificationException

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);

陷阱 2:for-each 變量作用域誤解

for (String s : list) { /* s 僅在循環體內可見 */ }
String s = "outer"; // 編譯錯誤:變量 s 已在外層作用域宣告
// for-each 循環變量隱含在循環體塊內,不洩露到外部

陷阱 3:基本類型陣列無法用 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)。

總結

  1. for-each 是 90%+ 遍歷場景的最佳選擇:語法簡潔、語義明確、防越界、支援所有 Iterable、無效能損耗。
  2. 傳統 for 保留 3 大核心優勢:索引訪問、反向遍歷、修改集合結構/基本類型陣列元素。
  3. 效能無差異 (陣列/ArrayList),LinkedList 必須用 for-each/Iterator。
  4. 現代代碼風格:優先 for-each → 複雜邏輯遷移 Stream → 極致性能/索引需求用傳統 for。

掌握這三種遍歷方式的邊界與優勢,寫出的代碼既符合現代 Java 風格,又能在性能敏感路徑做出正確抉擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved