Menu

Javaのコレクションを反復処理する: for-each、Iterator、forEach

Javaのコレクションをループする方法 - for-eachループ、Iterator、インデックスループ、forEachメソッド - と、反復中に要素を安全に削除する方法。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

1つの仕事、いくつかの道具

データを集めて - ArrayListHashSetHashMap の中に - これからすべての要素を訪れたいとします。Javaにはそのための方法がいくつかあり、どれを選ぶかは、インデックスが必要かどうか、ループの途中で要素を削除する必要があるかどうか、メソッド形式とループ形式のどちらの構文を好むかによって決まります。

良い知らせは、どの Collection(リスト、セット、キュー)も同じ拡張 for ループをサポートしているということです。だから一度覚えれば、すべて同じやり方で反復できます。

コロンは「の中の」と読みます。「langs の中の各 lang について」です。インデックスには一切触れないので、間違えようがありません。

for-eachループ

拡張 for は、単純に「各要素に対して何かをする」という場合の既定の選択肢です。読みやすく、コレクションの種類を問わず同じように動作します。ここではインデックスをまったく持たない HashSet を使います:

HashSet について覚えておくべきことが1つあります。定まった順序を持たないため、要素は任意の順序で出力される可能性があります。それでも for-each はすべての要素をちょうど一度だけ訪れます。

インデックスが必要なとき

for-each は要素を与えてくれますが、その位置は与えてくれません。本当にインデックスが必要なとき - 行に番号を振ったり、隣接する要素を見たりするとき - は size()get(i) を使ったカウント付きループを使います。これは位置を持つ List で動作します。セットとマップにはインデックスがないので、このスタイルは当てはまりません。

ただの習慣でこれに手を伸ばさないでください。iget(i) 以外に使っていないなら、for-each 版のほうが短く、間違いも起きにくいです。

Mapの反復処理

MapCollection ではないので、直接 for-each をかけることはできません。代わりに、3つのビューのいずれかをループします。もっとも一般的なのは entrySet() で、各キーと値のペアをまとめて渡してくれます:

キーだけが必要なら ages.keySet() を、値だけが必要なら ages.values() をループします。両方必要なときは entrySet() を選びましょう - キーをループして中で ages.get(key) を呼ぶと、毎回の反復で理由もなく2回目の検索をしてしまいます。

Iterator

for-each は実は Iterator の上に乗った糖衣構文です。IteratorhasNext()next() を通じてコレクションを1要素ずつたどるオブジェクトです。このループを手で書くことはめったにありませんが、1つ重要な例外があります。反復中に要素を削除する安全な方法だということです。

it.remove()next() が最後に返した要素を削除し、イテレータは有効なまま保たれます。これが、手書きのループ中にコレクションを変更する唯一の認められた方法です。

落とし穴: ConcurrentModificationException

for-eachループの中でコレクション自体に対して addremove を呼ぶと、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) { ... }。これは ArrayListHashSet などあらゆる Collection で動作します。get(i) を使うインデックスループは本当に位置が必要なときだけ使い、ループ中に要素を削除する必要があるときは Iterator を使いましょう。

JavaでHashMapを反復処理するにはどうすればいいですか?

MapCollection ではないので、そのビューのいずれかをループします。よく使うのは for (Map.Entry<K, V> e : map.entrySet()) で、キー(e.getKey())と値(e.getValue())を一度の走査で両方得られます。キーだけなら map.keySet() を、値だけなら map.values() をループすることもできます。

ループ中になぜConcurrentModificationExceptionが出るのですか?

for-eachループが反復している最中に、コレクションに対して addremove を呼んだためです。for-eachループは内部で Iterator を使っており、コレクションが構造的に変化したことを検出します。Iterator 自身の remove() メソッドで削除するか、ループの代わりに removeIf(...) を呼ぶことで解決できます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める