For-Each ループ


コレクションでの反復処理は、必要以上に厄介です。次のメソッドでは、タイマータスクのコレクションを取り、キャンセルしています。
void cancelAll(Collection<TimerTask> c) {
    for (Iterator<TimerTask> i = c.iterator(); i.hasNext(); )
        i.next().cancel();
}

イテレータを使用しても複雑になるだけです。さらに、エラーが発生する恐れもあります。イテレータ変数は各ループで 3 回発生しています。つまり、失敗する可能性は 2 回あることになります。for-each 構造体を使用すれば複雑さやエラーの危険性は排除されます。for-each 構造体の例を次に示します。

void cancelAll(Collection<TimerTask> c) {
    for (TimerTask t : c)
        t.cancel();
}

コロン (:) は「in (中の)」という意味です。上記のループは「c 内の TimerTask t ごと」ということになります。見てのとおり、for-each 構造体は、ジェネリクスときれいに結びついています。これにより、すべての型安全性が保持され、複雑さは取り除かれます。イテレータを宣言する必要がないため、イテレータのジェネリック宣言を行う必要もありません。プログラマに見えない所でコンパイラがジェネリック宣言を行いますが、プログラマはそのことを認識する必要はありません。

入れ子にされた反復を、2 つのコレクションに対して行おうとするときに犯しがちな、一般的な誤りを次に示します。

List suits = ...;
List ranks = ...;
List sortedDeck = new ArrayList();

// BROKEN - throws NoSuchElementException!
for (Iterator i = suits.iterator(); i.hasNext(); )
    for (Iterator j = ranks.iterator(); j.hasNext(); )
        sortedDeck.add(new Card(i.next(), j.next()));

どこにバグがあるのかわかりますか。経験豊富なプログラマの多くがこの間違いを経験しているので、わからなくても心配する必要はありません。問題は、next メソッドが「外側の」コレクション suits で何度も呼び出されていることです。外側と内側両方のコレクションのために、内側のループで next メソッドが呼び出されていますが、これが誤りです。修正するには、外側のループのスコープに変数を追加して、suit を保持させます。

// Fixed, though a bit ugly
for (Iterator i = suits.iterator(); i.hasNext(); ) {
    Suit suit = (Suit) i.next();
    for (Iterator j = ranks.iterator(); j.hasNext(); )
        sortedDeck.add(new Card(suit, j.next()));
}

それでは、上記の例は for-each 構造体と何の関係があるのでしょうか。入れ子になった反復があらかじめ用意されているのです。次の例を見てください。

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

for-each 構造体は、配列にも使用できます。この場合、イテレータではなくインデックス変数を隠すことができます。次のメソッドは、int 配列内の値の和を返します。

>// Returns the sum of the elements of a>
int sum(int[] a) {
    int result = 0;
    for (int i : a)
        result += i;
    return result;
}

for-each ループは、いつでも使用できます。使用すればコードがわかりやすくなります。ただし、どこにでも使用できるわけではありません。たとえば、expurgate メソッドについて考えてみましょう。現在の要素を削除するには、イテレータにアクセスする必要があります。for-each ループではイテレータが隠されるため、remove メソッドを呼び出すことができません。そのため、for-each ループをフィルタリングに使用することはできません。同様に、リストや配列内の要素をたどりながら置き換えるループには使用できません。また、複数のコレクションを並列に反復しなければならないループにも使用できません。ほとんどの場合に使用できる、整理された単純な構造体を意図的に採用した設計者にとって、これらの短所は既知の事項です。


Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.