Java

For-Each ループ

Language の目次


コレクションでの反復処理は、必要以上に厄介です。次のメソッドでは、タイマータスクのコレクションを取った後で、キャンセルしています。
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 ループをフィルタリングに使用することはできません。同様に、リストや配列内の要素をたどりながら置き換えるときには for ループを使用できません。また、複数のコレクションにまたがって並列に反復しなければならない for ループも使用できません。ほとんどの場合に使用できる、整理された単純な構造を意図的に採用した設計者にとって、これらの短所は既知の事項です。


Copyright © 2004 Sun Microsystems, Inc.All Rights Reserved.

コメントや提案をお寄せください。

Sun

Java ソフトウェア