はじめに
JavaのforEachは、ListやMapの要素に同じ処理を適用したい場面で、通常のfor文より短く書ける構文です。そのため、出力、変換、集計、条件分岐を小さな処理としてまとめやすくなり、コーディングスピードを上げたい学習者にも扱いやすい選択肢になります。
ただし、すべてのループをforEachへ置き換える必要はありません。breakやcontinueで途中制御したい処理、インデックスを強く意識する処理、例外処理が複雑な処理では、通常のfor文やstream()との使い分けが読みやすさを左右します。
このとき基準になるのは、処理の目的が副作用なのか、値の変換なのかという点です。出力や通知のような副作用ならforEach、新しいコレクションを作るならmapやfilterを組み合わせる、と整理すると使い方を誤りにくくなるのが基本です。
公式ドキュメントによれば、Iterable.forEachは各要素に渡されたConsumerを適用します。詳しい仕様はOracle Java SE 17 IterableとOracle Java SE 17 Streamで確認できます。
- Java 17 LTS / OpenJDK 17
- 標準ライブラリ: java.util / java.util.stream / java.util.concurrent
- IDEやオンライン実行環境では、クラス名をMainに合わせる構成を想定
- Javaの
forEachと通常のループの使い分け List、Map、Streamでの基本的な使い方- 集計、変換、非同期処理などの応用例
null、例外、並列処理に関する注意点- 独自メソッドやフレームワーク連携によるカスタマイズ
Javaとは
Javaは、クラスを中心にプログラムを組み立てるオブジェクト指向のプログラミング言語です。その実行形式はjavacで.classへコンパイルされ、JVM上で動くため、環境差を吸収しやすい構成になっています。
一般に、Javaのコードはclass、method、field、interfaceを組み合わせて表現します。そのため、処理を再利用しやすく、業務アプリケーション、Android関連、サーバーサイド開発などで幅広く使われますし、ここがポイントです。
これらの基礎に加えて、Java 8からはラムダ式、Stream API、default methodが広く使われるようになりました。forEachはその流れの中で利用頻度が高まったメソッドであり、短いループ処理を読みやすく書く入口になります。
初心者がつまずきやすいのは、拡張for文、Iterable.forEach、Stream.forEachを同じものとして扱ってしまう点です。見た目は似ていますが、戻り値、順序、例外、並列実行時の扱いに違いがあります。
Javaの基本概念
Javaのクラスはオブジェクトの設計図にあたり、newで作られたインスタンスが実際の処理を持ちますが、これは押さえたい点です。この構造により、データと処理を近い場所へ置き、変更範囲を読み取りやすくできます。
一方、コレクションを扱う処理では、ArrayList、HashMap、Setなどの標準APIが頻繁に登場します。その要素を順に処理するときにforEachを選ぶと、意図を短く表しやすくなるのが目安です。
ただし、Javaは型を明示する言語なので、ラムダ式の中で受け取る値の型もコレクションの宣言に依存します。List<String>なら要素はStringとして扱われ、Map<String,Integer>ならキーと値の型が処理の前提になります。
forEachメソッドとは
forEachメソッドは、コレクション内の要素に対して同じ処理を順に適用するためのメソッドです。JavaのIterableに用意されたdefault void forEach(Consumer<? super T> action)は、要素を取り出しながらConsumer.acceptを呼び出す形で動きますし、これが一つの目安です。
具体的には、画面出力、ログ出力、既存オブジェクトのメソッド呼び出しのように、戻り値を作らず処理を進めたい場面で相性が良くなります。コーディングスピードを優先したい短い処理では、波かっこを省いたラムダ式も使えます。
💡 Tips:forEachは値を返さない終端処理として使うのが自然です。新しいリストを作る処理では、stream()、filter()、map()、collect()を組み合わせるほうが意図を読み取りやすくなるのがポイントです。
基本の書き方
基本形は、対象のコレクションに対してforEachを呼び、引数へラムダ式を渡します。そのラムダ式の左側に要素名、右側に処理を書くため、通常のループより記述量を抑えられます。
結果: 期待される出力は、apple、banana、cherryが順に表示される内容です。
このサンプルコードでは、Arrays.asListで作ったリストに対してforEachを呼びます。そのため、要素を取り出すためのint iやget(i)を書かずに済み、短い処理ほどコーディングスピードの差が出やすくなります。
動作の概要
その動作は、拡張for文に近い形で理解できるのが一般的です。各要素がラムダ式の引数へ入り、波かっこの中に書いた処理が要素数だけ呼び出されます。
ただし、Stream.forEachとIterable.forEachでは性質が異なります。parallel()を含むStreamでは処理順が保証されない場合があるため、順序が必要な処理ではforEachOrderedも候補になるのが現実的です。
結果: これは期待される出力例です。実際のJavaコードとして実行する部分ではなく、直前のサンプルコードで想定されるコンソール表示を表しています。
このような小さな出力処理なら、forEachの使い方は直感的です。一方、処理途中で停止する必要がある場合は、returnがラムダ式から抜けるだけで外側のメソッド全体を終了しない点に注意します。
| 用途 | 主なAPI | 向いている処理 | 注意点 | 関連 |
|---|---|---|---|---|
| 単純出力 | forEach | 各要素の表示 | 副作用中心になる | List型 |
| キーと値 | Map.forEach | ペア処理 | HashMapは順序保証なし | エスケープ処理 |
| 条件分岐 | if | 偶数だけ表示 | 複雑なら分離 | 条件判定 |
| 変換 | map | 大文字化 | forEachだけで完結させない | アノテーション |
| 絞り込み | filter | 条件抽出 | 副作用を減らす | オーバーライド |
| 集計 | sum | 合計計算 | AtomicIntegerの意味を理解 | 応用例 |
| 文字列連結 | StringBuilder | 表示文作成 | 末尾記号の処理 | サンプルコード |
| 例外処理 | try | 変換失敗の吸収 | チェック例外は工夫が必要 | 注意点 |
| null対策 | Objects | 事前判定 | NullPointerException回避 | 注意点 |
| 並列処理 | parallel | 独立処理 | 順序と共有状態 | カスタマイズ |
| 順序維持 | forEachOrdered | 並列時の順序 | 速度より順序 | ループ |
| インデックス | IntStream.range | 複数リスト対応 | サイズ差に注意 | 応用例 |
| メソッド参照 | System.out::println | 短い出力 | 処理が単純な場合に限定 | 使い方 |
| 外部変数 | final | 参照のみ | 再代入できない | 注意点 |
| 可変保持 | AtomicInteger | カウンタ | 通常処理では過剰な場合あり | 応用例 |
| リスト更新 | set | 要素差し替え | 重複値に注意 | サンプルコード |
| 削除処理 | removeIf | 条件削除 | forEach中の削除は避ける | 注意点 |
| 非同期 | CompletableFuture | 独立タスク | 完了待ちが必要 | 応用例 |
| DB処理 | JdbcTemplate | 行ごとの処理 | 戻り値設計 | カスタマイズ |
| 独自処理 | Consumer | 共通前処理 | 過度な抽象化を避ける | カスタマイズ |
| 配列 | Arrays.stream | 配列の走査 | プリミティブ型に注意 | 使い方 |
| ログ | logger | 記録処理 | 大量出力に注意 | 注意点 |
| 検証 | Predicate | 条件関数 | 命名で意図を明確化 | 応用例 |
| 変換結果 | collect | 新リスト作成 | 終端操作の選択 | 使い方 |
| 重複除外 | distinct | 一意化 | 順序の前提を確認 | 応用例 |
| ソート後処理 | sorted | 順序付け | 比較関数の確認 | ループ |
| 範囲生成 | rangeClosed | 連番処理 | 終端値を確認 | サンプルコード |
| 型変換 | parseInt | 文字列数値化 | 例外処理 | 注意点 |
| 数学処理 | Math.sqrt | 平方根 | NaNの扱い | 応用例 |
| 設計判断 | for | 途中終了 | 読みやすさ優先 | 使い方 |
forEachメソッドの使い方
JavaでforEachの使い方を覚えるときは、単純な出力から始め、次に条件分岐、外部変数、Map、Streamへ広げる流れが理解しやすくなります。その順序で学ぶと、ループ処理の置き換えどころと置き換えないほうがよい箇所も見えます。
具体的には、下のサンプルコード群で、短い処理、状態を持つ処理、複数操作を含む処理を比べますが、覚えておくと役立つでしょう。どの例でも、コードブロック直後に期待される出力を示します。
サンプルコード1:基本的な使い方
基本のループ処理では、各要素を1回ずつ取り出して表示します。number -> System.out.println(number)の形なら、ラムダ式の本体が1文だけなので波かっこを省けますし、ここを基本と考えるとよいでしょう。
結果: 期待される出力は、1から5までの数字が順番に表示される内容です。
この形は、JavaのListに入った値を読むだけの処理に向いています。出力、ログ、単純なメソッド呼び出しのように副作用が目的なら、コーディングスピードを落とさず読みやすく書けます。
サンプルコード2:リスト内の要素を変更する
リストの要素を書き換える場合は、読み取りだけの処理より注意点が増えますし、ここがポイントです。特にindexOfは同じ値が複数あると最初の位置を返すため、重複値を含むリストでは意図しない更新になる可能性があります。
結果: 期待される出力は[11, 12, 13]です。重複値がない前提では、各要素に10が加算されたリストになります。
そのため、要素更新を本格的に扱うなら、IntStream.rangeでインデックスを明示する方法や、新しいリストへ変換結果を詰める方法も候補になります。forEachだけで無理に変更処理を完結させない判断が、読みやすさを保ちますが、これは押さえたい点です。
サンプルコード3:Mapに対するforEach
Map.forEachでは、ラムダ式の引数がキーと値の2つになります。HashMapは出力順を保証しないため、順序が必要ならLinkedHashMapやTreeMapを検討します。
結果: 期待される出力は、各学生名と得点の組み合わせです。HashMapでは表示順がコードに書いた追加順と一致するとは限りません。
この例では、キーをname、値をscoreとして受け取ります。名前と点数のようにペアで意味を持つデータは、通常のループよりMap.forEachのほうが処理対象を読み取りやすい場合があります。
サンプルコード4:Streamと組み合わせる
Streamを使うと、処理をfilter、map、forEachのように段階へ分けられますし、これが一つの目安です。この構成では、絞り込みはfilter、最後の表示はforEachが担当します。
結果: 期待される出力は、条件に合う果物名です。文字数の数え方はJavaのString.length()が返すchar数に基づきます。
このサンプルコードのように、条件で絞ってから表示する処理は、通常のループより宣言的に読めます。一方、条件分岐が複雑になりすぎるなら、判定を別メソッドに分けると見通しが良くなると整理できます。
サンプルコード5:条件分岐を組み込む
forEachの内部にもif文を書けます。偶数だけ出力するような短い条件なら、ループ全体の意図を損なわずに表現できます。
結果: 期待される出力は2、4、6、8、10です。
ただし、条件が増えるほどラムダ式の中が読みにくくなります。その場合は、filter(number -> number % 2 == 0)で絞り込んでからforEachで表示する書き方も検討できます。
サンプルコード6:外部変数との連携
ラムダ式から外側のローカル変数を参照する場合、その変数は実質的にfinalである必要があると理解できます。再代入はできませんが、StringBuilderのようなオブジェクトの中身を変更することはできます。
結果: 期待される出力は田中さん、鈴木さん、佐藤さんです。
この使い方では、builderという参照自体は差し替えず、内部の文字列だけを更新します。複数スレッドで共有する処理では別の考慮が必要ですが、単一スレッドの文字列組み立てなら理解しやすい例になります。
サンプルコード7:複数の操作を組み合わせる
一度のforEachで出力と集計を同時に行うこともできると覚えるとよいでしょう。このとき、外部の数値を直接再代入できないため、配列やAtomicIntegerのような保持先が使われる場合があります。
結果: 期待される出力は偶数: 2、偶数: 4、偶数の合計: 6です。
このパターンは学習用として分かりやすい一方、集計だけならstream()、filter()、mapToInt()、sum()のほうが目的を明確に表せます。コーディングスピードだけでなく、後から読む人の理解速度も判断材料になります。
サンプルコード8:独自の処理を追加する
各要素から文字列を作り、別のリストへ入れる処理も書けると考えられます。ただし、新しいリストを作る処理はmapとcollectでも表現できるため、どちらが自然かを比べるとよいでしょう。
結果: 期待される出力は、各名前への挨拶文と、生成された挨拶リスト: [Hello, Alice!, Hello, Bob!, Hello, Charlie!]です。
このサンプルコードは、表示とリスト追加という複数の副作用を含みます。処理が増えすぎる場合は、挨拶文を作る部分をcreateGreetingのようなメソッドへ分けると、カスタマイズしやすくなります。
サンプルコード9:エラーハンドリングを含める
forEachの中でもtry-catchは書けると言えるでしょう。もっとも、下の例で使うMath.sqrtは負の数に対して例外ではなくNaNを返すため、例外処理だけでは負値の検出にならない点を理解しておく必要があります。
結果: 期待される出力は平方根: 2.0、平方根: NaN、平方根: 3.0です。負の数をエラー表示にしたい場合は、if (number < 0)で事前判定します。
この注意点は、JavaのAPI仕様を前提にした読み替えが必要な例です。例外が起きると決めつけず、対象メソッドが返す値と例外条件を公式ドキュメントで確認すると、誤ったサンプルコードを避けられます。
forEachメソッドの応用例
forEachの応用例では、単純なループ処理から一歩進み、集計、変換、複数リストの同期、非同期処理、オブジェクトのメソッド呼び出しを扱いるのが基本です。Javaの実務的なコードでは、これらを組み合わせる場面が多くなります。
サンプルコード1:集計処理を扱う
数値を足し込む処理では、AtomicIntegerを使って外部状態を更新できます。ただし、単純な合計ならmapToIntとsumも自然な選択肢です。
結果: 期待される出力は合計値: 15です。
この例のAtomicIntegerは、値を保持して加算する役割を持ちます。並列処理を含まない単純な集計では過剰になる場合もあるため、コードの意図に合わせて選ぶことが大切です。
サンプルコード2:フィルタリングとマッピング
文字列を条件で除外し、残った値を大文字へ変換する応用例です。forEach内でstartsWithとtoUpperCaseを使い、新しいリストへ結果を追加します。
結果: 期待される出力は大文字化した結果:[APPLE, CHERRY]です。
一方、この処理はstream().filter(...).map(...).collect(...)でも表現できます。新しいデータを作る目的が中心なら、変換用APIを使ったほうが副作用を減らせます。
サンプルコード3:複数リストの同時処理
複数リストを同時に扱う場合、forEach単体ではインデックスを直接受け取れません。そのため、IntStream.rangeで番号を作り、その番号から各リストの要素を取得するのが目安です。
結果: 期待される出力は、同じ位置にある名前と年齢を組み合わせた行です。2つのリストの長さが異なる場合は、事前にサイズを確認します。
結果: これは期待される出力例です。IntStream.rangeで作ったiを使い、同じインデックスの要素を取得しています。
この応用例は、CSVの列や画面表示用データを組み合わせる場面に近い考え方です。ただし、関連する値は専用クラスへまとめたほうが、後のカスタマイズや保守では扱いやすくなるのがポイントです。
サンプルコード4:非同期処理での使い方
CompletableFuture.runAsyncを使うと、各要素に対する処理を非同期タスクとして起動できます。ただし、下のコードは完了待ちを入れていないため、実行環境によっては出力前にプログラムが終了する可能性があります。
結果: 期待される出力は非同期メッセージ: Hello、非同期メッセージ: World、非同期メッセージ: Javaです。順序は保証されない場合があります。
CompletableFuture.allOf(...).join()のような完了待ちを設計に含めます。サンプルコード5:オブジェクト指向との組み合わせ
Javaらしい使い方として、オブジェクトのメソッドを各要素で呼び出すパターンがあるのが一般的です。PersonのリストへforEachを適用し、それぞれのintroduceを呼び出します。
結果: 期待される出力は、3人分の自己紹介文です。
結果: これは期待される出力例です。各Personオブジェクトのintroduce()が順番に呼ばれます。
このように、処理をオブジェクト側へ寄せると、forEach内のラムダ式は短くなります。メソッド参照を使える場合はpeople.forEach(Person::introduce)のように書くこともできるのが現実的です。
注意点と対処法
forEachは短く書けますが、注意点を知らずに使うと、例外、順序、共有状態、途中終了の扱いでつまずきます。特に押さえたいのは、nullの扱い、ラムダ式の例外、並列処理、コレクション変更です。
nullの扱い
nullのコレクションに対してforEachを呼ぶと、NullPointerExceptionが発生します。メソッド呼び出しの対象が存在しないため、ラムダ式の中身に進む前に失敗すると整理できます。
結果: 期待される動作は、NullPointerExceptionが発生することです。itemsがnullのため、forEachを呼べません。
そのため、入力値がnullになる可能性がある処理では、呼び出し前に判定します。空のリストを返す設計にできるなら、呼び出し側の分岐を減らせます。
結果: 期待される出力はありません。itemsがnullなので、ifの内部処理は実行されません。
エラーハンドリング
ラムダ式の中で例外が起きる処理を扱う場合、try-catchの場所を決める必要があります。文字列を数値へ変換する例では、Integer.parseIntが変換できない文字列に対してNumberFormatExceptionを投げます。
結果: 期待される出力は1、2、three は数値に変換できません。、4です。
この対処法では、変換失敗を1要素分のエラーとして扱い、ほかの要素の処理を続けます。逆に、1件でも失敗したら全体を止めたい場合は、ラムダ式の外側で設計を分けるほうが明確です。
スレッドセーフについて
一般的なArrayListやHashMapは、複数スレッドから同時に変更される前提ではありません。そのため、parallelStreamや非同期処理と組み合わせると、共有状態の更新で問題が起きる可能性があります。
このときは、ConcurrentHashMap、CopyOnWriteArrayList、AtomicIntegerのような並行処理向けAPIを検討すると理解できます。ただし、スレッドセーフな型を使えば常に速くなるわけではなく、処理の粒度と共有状態の有無が判断材料になります。
パフォーマンス面での注意
大量データを扱う場合、forEachそのものより、ラムダ式の中で行う処理がボトルネックになることがあります。特に標準出力、ファイルI/O、データベースアクセスは、ループ構文を変えても根本的な待ち時間が残りますが、覚えておくと役立つでしょう。
結果: 期待される表示はありません。parallel()で並列ストリームを作り、各数値に対する処理枠だけを示しています。
ただし、並列化は順序と共有状態の注意点を増やします。順番が必要な処理ではforEachOrderedを検討し、状態を更新する処理では競合が起きない設計にすると覚えるとよいでしょう。
HashMapが順序を保証しないことも明記されています。順序が意味を持つ出力では、Oracle Java SE 17 HashMapの仕様も確認しておくと安全です。カスタマイズ方法
forEachのカスタマイズでは、共通処理を独自メソッドにまとめる方法と、既存ライブラリが提供するコールバック形式へ理解を広げる方法があります。どちらもConsumerやラムダ式の考え方を使うため、基本の使い方がそのまま土台になると考えられます。
独自のforEachメソッドを作成する
独自のcustomForEachを作ると、各要素の処理前にログ出力や検証を差し込めます。引数にConsumer<T>を受け取る設計にすると、呼び出し側はラムダ式で任意の処理を渡せます。
結果: 期待される出力は、各名前の前にProcessing:が表示され、その後に挨拶文が表示される内容です。
結果: これは期待される出力例です。customForEachが共通の前処理を行い、渡されたactionが要素ごとの処理を担当します。
このカスタマイズは、処理の入口を統一したいときに役立ちます。一方、独自メソッドを増やしすぎると標準APIから離れるため、チーム内で意図が共有できる範囲に留めるのが現実的です。
既存のライブラリやフレームワークとの連携
Javaのライブラリには、forEachと似た発想でコールバックを受け取るAPIがあると言えるでしょう。Spring FrameworkのJdbcTemplate.queryも、行ごとの処理をラムダ式で渡す構成にできます。
結果: 期待される出力は、usersテーブルから取得された各行のIDと名前です。実際の表示内容はデータベース内のレコードに依存します。
この例では、データベース接続、テーブル定義、Springの設定が前提になります。標準Javaだけで完結するサンプルコードではないため、導入時は依存関係と設定ファイルを確認するのが基本です。
同様に、Webアプリケーションやバッチ処理では、ライブラリ側が用意したコールバックAPIにラムダ式を渡す場面が増えます。forEachの理解は、そうしたAPIの読み解きにもつながります。
まとめ
JavaのforEachは、コレクションの要素へ同じ処理を適用するループ表現として、短く読みやすいコードを書きたい場面に合いるのが目安です。特に出力、ログ、オブジェクトのメソッド呼び出しでは、通常のfor文より処理の目的を前面に出せます。
一方、値の変換や新しいリスト作成が目的なら、filter、map、collectを組み合わせる使い方が自然です。その使い分けができると、サンプルコードを写す段階から抜け出し、処理の目的に合ったJavaコードへ近づけます。
注意点として、nullのコレクション、HashMapの順序、ラムダ式内の例外、外部変数の扱い、並列処理の共有状態は見落としやすい部分です。これらを押さえると、forEachの使い方を安全に広げられますし、ここを基本と考えるとよいでしょう。
応用例やカスタマイズまで理解すると、コーディングスピードを上げながら、読み手にも意図が伝わるループ処理を書きやすくなります。Javaの学習では、for文、拡張for文、forEach、Streamを比較しながら使うと整理しやすくなります。
関連記事
- Java List型完全ガイド!初心者でもマスターできる7つのステップ
- Javaアノテーションの12選!初心者から上級者まで徹底ガイド
- Javaでうるう年を判定!初心者でも分かる9ステップ解説
- Javaエスケープ処理の10ステップマスターガイド
- Javaでマスターする!オーバーライドのたった7つのステップ
※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。


