1つの仕事、いくつかの道具
データを集めて - ArrayList、HashSet、HashMap の中に - これからすべての要素を訪れたいとします。Javaにはそのための方法がいくつかあり、どれを選ぶかは、インデックスが必要かどうか、ループの途中で要素を削除する必要があるかどうか、メソッド形式とループ形式のどちらの構文を好むかによって決まります。
良い知らせは、どの Collection(リスト、セット、キュー)も同じ拡張 for ループをサポートしているということです。だから一度覚えれば、すべて同じやり方で反復できます。
コロンは「の中の」と読みます。「langs の中の各 lang について」です。インデックスには一切触れないので、間違えようがありません。
for-eachループ
拡張 for は、単純に「各要素に対して何かをする」という場合の既定の選択肢です。読みやすく、コレクションの種類を問わず同じように動作します。ここではインデックスをまったく持たない HashSet を使います:
HashSet について覚えておくべきことが1つあります。定まった順序を持たないため、要素は任意の順序で出力される可能性があります。それでも for-each はすべての要素をちょうど一度だけ訪れます。
インデックスが必要なとき
for-each は要素を与えてくれますが、その位置は与えてくれません。本当にインデックスが必要なとき - 行に番号を振ったり、隣接する要素を見たりするとき - は size() と get(i) を使ったカウント付きループを使います。これは位置を持つ List で動作します。セットとマップにはインデックスがないので、このスタイルは当てはまりません。
ただの習慣でこれに手を伸ばさないでください。i を get(i) 以外に使っていないなら、for-each 版のほうが短く、間違いも起きにくいです。
Mapの反復処理
Map は Collection ではないので、直接 for-each をかけることはできません。代わりに、3つのビューのいずれかをループします。もっとも一般的なのは entrySet() で、各キーと値のペアをまとめて渡してくれます:
キーだけが必要なら ages.keySet() を、値だけが必要なら ages.values() をループします。両方必要なときは entrySet() を選びましょう - キーをループして中で ages.get(key) を呼ぶと、毎回の反復で理由もなく2回目の検索をしてしまいます。
Iterator
for-each は実は Iterator の上に乗った糖衣構文です。Iterator は hasNext() と next() を通じてコレクションを1要素ずつたどるオブジェクトです。このループを手で書くことはめったにありませんが、1つ重要な例外があります。反復中に要素を削除する安全な方法だということです。
it.remove() は next() が最後に返した要素を削除し、イテレータは有効なまま保たれます。これが、手書きのループ中にコレクションを変更する唯一の認められた方法です。
落とし穴: ConcurrentModificationException
for-eachループの中でコレクション自体に対して add や remove を呼ぶと、ConcurrentModificationException という実行時例外が発生します。イテレータは自分の足元でコレクションが変化したことに気づき、続行を拒否するのです。これは初心者が最もよく犯す誤りの1つです。
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
if (n % 2 == 0) {
nums.remove(Integer.valueOf(n)); // throws ConcurrentModificationException
}
}
解決策はほとんどの場合 removeIf です。意図を1行で表現し、反復をあなたの代わりに処理してくれます:
removeIf はあらゆる Collection で動作するので、同じ呼び出しで HashSet も整理できます。
forEachメソッド
どのコレクションにも、ラムダを受け取って各要素に対して実行する forEach メソッドもあります。ループに比べてより関数的で式スタイルの代替手段で、短い一行処理に便利です:
Map.forEach は2引数のラムダ (key, value) を直接受け取る点に注意してください - entrySet() は不要です。素早い副作用には forEach を使い、本体が大きくなったり早めに抜けるために break が必要になったりしたら通常の for ループに戻りましょう。ラムダでは break はできません。
次は: メソッド
ここまでで、データをコレクションにまとめ、Javaが提供するあらゆる方法でそれをたどってきました。次のステップは、振る舞いをまとめることです。自分でメソッドを書くことで、ロジックのまとまりに名前を付け、入力を渡し、再利用できるようになります - それが次のページです。
よくある質問
Javaでリストをループするにはどうすればいいですか?
もっともすっきりした方法は拡張for文(for-each)です: for (String s : list) { ... }。これは ArrayList、HashSet などあらゆる Collection で動作します。get(i) を使うインデックスループは本当に位置が必要なときだけ使い、ループ中に要素を削除する必要があるときは Iterator を使いましょう。
JavaでHashMapを反復処理するにはどうすればいいですか?
Map は Collection ではないので、そのビューのいずれかをループします。よく使うのは for (Map.Entry<K, V> e : map.entrySet()) で、キー(e.getKey())と値(e.getValue())を一度の走査で両方得られます。キーだけなら map.keySet() を、値だけなら map.values() をループすることもできます。
ループ中になぜConcurrentModificationExceptionが出るのですか?
for-eachループが反復している最中に、コレクションに対して add や remove を呼んだためです。for-eachループは内部で Iterator を使っており、コレクションが構造的に変化したことを検出します。Iterator 自身の remove() メソッドで削除するか、ループの代わりに removeIf(...) を呼ぶことで解決できます。