The King's Museum

ソフトウェアエンジニアのブログ。

【Effective Java】項目46:従来の for ループより for-each を選ぶ

for ループよりも for-each ループを利用しましょう。

Java 5 よりも前では、コレクションと配列を走査する好ましい方法は以下の通りでした。

// コレクションの走査
for (Iterator i = c.iterator; i.haxNext(); ) {
    doSomething((Element) i.next());
}

// 配列の走査
for (int i = 0; i < a.length; i++) {
    doSomething(i);
}

Java 5 では for-each が導入されました。 そのおかげで、コレクションも配列も次のように走査できるようになりました。

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

この for-each ループはインデックスの上限の計算を一度しか行いません。 そのため、パフォーマンス上のペナルティは存在しません。

for ループでの間違い

従来の for ループでは、コレクションに対してネストした走査を行う場合によく間違いが起こりました。

enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
            NINE, TEN, JACK, QUEEEN, KING }
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values());

List<Card> deck = new ArrayList<>();
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() が呼び出されているので、意図したよりもはやく Suit が先に進んでしまいます。

正しくは、次のように一度変数に値を代入しなければなりません。

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

Iterable インタフェース

Iterable というインタフェースを実装すると、どんなオブジェクトでも for-each を利用することができます。

Iterable は単一のメソッドから構成されるため、これを実装するのはそれほど難しくありません。 Collection を実装しないとしても Iterable を実装すればユーザーは多大な恩恵を受けることができます。

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

for-each が利用できない場合

一般的に for ループよりも for-each ループは優れています。

しかし、次のような場合には for-each ループは利用できません。 このような場合は従来通り気をつけて for ループを利用してください。

  • フィルタリング:特定の要素をコレクションから削除する場合
  • 変換:特定の要素を他の値に置換する場合
  • 並行イテレーション:複数のコレクションを並列に走査する場合

(c) The King's Museum