void cancelAll(Collection<TimerTask> c) { for (Iterator<TimerTask> i = c.iterator(); i.hasNext(); ) i.next().cancel(); }
イテレータを使用しても複雑になるだけです。さらに、エラーが発生する恐れもあります。イテレータ変数は各ループで3回発生しています。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
で何度も呼び出されていることです。外側と内側両方のコレクションのために、内側のループで呼び出されていますが、これが誤りです。修正するには、外側のループのスコープに変数を追加して、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ループをフィルタリングに使用することはできません。同様に、リストや配列内の要素をたどりながら置き換えるループには使用できません。また、複数のコレクションを並列に反復しなければならないループにも使用できません。ほとんどの場合に使用できる、整理された単純な構造体を意図的に採用した設計者にとって、これらの短所は既知の事項です。