ITEM 58:for-eachの使用を考えてからforを考える

3950 ワード

ITEM 58:PREFER FOR-EACH LOOPS TO TRADITIONAL FOR LOOPS item 45で議論したように、一部のタスクはストリームによって完了することが望ましいが、他のタスクは反復によって完了する.これは伝統的なforループの集合です.
for (Iterator i = c.iterator(); i.hasNext(); ) { 
  Element e = i.next();
  ... // Do something with e
}

これは伝統的なforサイクルで配列を巡ります.
// Not the best way to iterate over an array!
for (int i = 0; i < a.length; i++) { 
  ... // Do something with a[i]
}

これらの習慣的な使い方はwhileサイクルよりも良い(item 57)が、完璧ではない.反復器とインデックス変数は混乱しています.必要なのは要素だけです.また、間違いを犯す機会を表しています.反復器は、各ループに3回、インデックス変数に4回表示されます.これにより、誤った変数を使用する機会が多くなります.そうすれば、コンパイラが問題をキャプチャする保証はありません.最後に,この2つのサイクルは非常に異なり,容器タイプに不必要な注意を引き起こし,このタイプを変更する小さなトラブルを増大させた.これらの問題はすべて解決されました.反復器またはインデックス変数を非表示にすることで、混乱やエラーを回避します.これによって生じる慣用法は、集合および配列にも適用され、容器の実装タイプを1つから別のプロセスに変換することを簡略化します.
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
  ... // Do something with e 
}

コロン(:)が表示されたら、inと読みます.したがって、上記のループは「要素内の各要素eについて」と読みます.「for-eachループを使用すると、配列に対してもパフォーマンスが損なわれません.生成されたコードは本質的に手動で作成されたコードと同じです.
// Can you spot the bug?
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
NINE, TEN, JACK, QUEEN, KING } ...

static Collection suits = Arrays.asList(Suit.values()); 
static Collection ranks = Arrays.asList(Rank.values());

List deck = new ArrayList<>();
for (Iterator i = suits.iterator(); i.hasNext(); )
  for (Iterator j = ranks.iterator(); j.hasNext(); ) 
    deck.add(new Card(i.next(), j.next()));

もしあなたが間違いを発見しなかったら、悲しんではいけません.多くのプロのプログラマーはこのような間違いを犯したことがあります.問題は、外部コレクション(suits)について、次のメソッドが反復器で複数回呼び出されることです.色ごとに呼び出されますが、内部から呼び出されるので、カードごとに呼び出されます.suitが切れたら、NoSuchElementExceptionをループして放出します.  非常に不幸で、外部コレクションのサイズが内部コレクションのサイズの倍数である場合(同じコレクションである可能性があるため)、ループは正常に終了しますが、目的の操作は実行されません.例を挙げると、1対のサイコロの可能なすべてのサイコロを投げた結果を印刷する誤った試みを考えてみましょう.
// Same bug, different symptom!
enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX } ...

Collection faces = EnumSet.allOf(Face.class);
for (Iterator i = faces.iterator(); i.hasNext(); ) 
  for (Iterator j = faces.iterator(); j.hasNext(); )
    System.out.println(i.next() + " " + j.next());

プログラムは例外を投げ出すことはありませんが、36の組み合わせではなく6つの「double」(ONE ONEからsix sixまで)しか印刷されません.これらの例のエラーを修正するには、外層要素を保存するために、外層サイクルの範囲内に変数を追加する必要があります.
// Fixed, but ugly - you can do better!
for (Iterator i = suits.iterator(); i.hasNext(); ) { 
  Suit suit = i.next();
  for (Iterator j = ranks.iterator(); j.hasNext(); ) 
    deck.add(new Card(suit, j.next()));
}

ネストされたfor-eachループを使用すると、問題は消えます.結果のコードは簡潔です.
// Preferred idiom for nested iteration on collections and arrays
for (Suit suit : suits)
  for (Rank rank : ranks) 
    deck.add(new Card(suit, rank));

残念なことに、for-each: *破壊フィルタを使用することはできません.選択した要素を削除するためにセットを巡回する必要がある場合は、明示的な反復器を使用して削除方法を呼び出す必要があります.Java 8に追加されたCollectionのremoveIfメソッドを使用することで、通常は明示的な遍歴を回避できます.  *変換:リストまたは配列を巡回し、要素の一部または全部の値を置換する必要がある場合は、リスト反復器または配列インデックスを使用して要素の値を置換する必要があります.複数のセットを並列に巡回する必要がある場合は、反復またはインデックス変数を明示的に制御して、すべての反復またはインデックス変数を同期させる必要があります(上記のエラーカードやサイコロの例で何気なく説明したように).これらの状況のいずれかに気づいた場合は、通常のforループを使用して、本プロジェクトで説明したトラップに注意してください.for-eachループでは、集合と配列だけでなく、単一のメソッドで構成されたIterableインタフェースを実装する任意のオブジェクトを巡回できます.インタフェースは次のとおりです.
public interface Iterable {
// Returns an iterator over the elements in this iterable 
  Iterator iterator();
}

自分の反復器を最初から書かなければならない場合は、Iterableは少し実現しにくいです.しかし、要素のセットを表すタイプを書いている場合は、集合を実装しないことを選択しても、Iterableを実装することを絶対に考慮する必要があります.これにより、for-eachループループタイプを使用することができ、いつまでも感謝しきれません.要するに、for−eachサイクルは、従来のforサイクルと比較して、解像度、柔軟性、バグ予防において注目される利点を提供し、性能損失をもたらさない.for loopsではなくfor-eachサイクルをできるだけ使用します.