整理 Effective Java 書中 Item 58: Prefer for-each loops to traditional for loops 心得筆記

主旨

在 Java 中處理陣列或集合時,for-each 迴圈(也叫 enhanced for loop)比傳統的 for 迴圈更簡潔、更安全、更容易維護。除非遇到特定場景(例如刪除元素、修改值、平行遍歷多集合),否則應優先使用 for-each。

點出問題

傳統 for 寫法看起來沒錯,但潛藏許多風險:

for (int i = 0; i < a.length; i++) {
    doSomething(a[i]);
}

for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
    doSomething(e);
}

問題在哪?

  • 變數(i, e, a[i])是雜訊,會干擾閱讀理解
  • 多處重複使用變數,容易出錯
  • 若改變資料結構(例如從陣列改為集合),要改很多地方

for-each 迴圈的優勢

for (Element e : elements) {
    doSomething(e);
}

優點總結:

  • 語法簡潔,無需 index 或 iterator
  • 不易出現 off-by-one 錯誤或 next() 調用錯誤
  • 對集合與陣列都適用
  • 對巢狀迴圈更友善

巢狀迴圈常見錯誤

以下範例會錯誤地重複呼叫外層迭代器,導致 NoSuchElementException 或邏輯錯誤:

for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
    deck.add(new Card(i.next(), j.next())); // 錯誤!i.next() 被呼叫太多次

正確做法(但冗長):

for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {
    Suit suit = i.next();
    for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
        deck.add(new Card(suit, j.next()));
}

最佳做法:使用 for-each

for (Suit suit : suits)
for (Rank rank : ranks)
    deck.add(new Card(suit, rank));

這樣不但簡潔,還避免了複雜的變數錯誤。

哪些情況不適合 for-each?

  1. 刪除元素(destructive filtering)

    • 要使用 iterator.remove(),for-each 無法做到

    • 可改用 Java 8 的 removeIf

      list.removeIf(e -> shouldRemove(e));
      
  2. 元素變更(transforming)

    • 若要修改 list 裡的元素值,還是需要用 index:

      for (int i = 0; i < list.size(); i++) {
          list.set(i, transform(list.get(i)));
      }
      
  3. 平行遍歷多集合(parallel iteration)

    • 需要同步前進兩個 iterator/index,例如 zip 兩個集合時

Iterable 介面支援 for-each

只要物件有實作 Iterable 介面,就能使用 for-each:

public interface Iterable<E> {
    Iterator<E> iterator();
}

建議:若寫的是「可遍歷元素的類別」,不一定要實作 Collection,但至少該實作 Iterable,方便使用。

小結

for-each 是寫迴圈的推薦方式,有以下優點:

  • 可讀性高
  • 減少錯誤機率
  • 容易維護與 refactor
  • 無性能損耗

使用原則:

✅ 能用 for-each 就用 for-each
❌ 除非需要刪除、修改、同步遍歷,不要用傳統 for

乾淨的迴圈邏輯,會讓程式維護起來輕鬆愉快!