AIによる要約
Javaには通常のfor文、拡張for文、Streamなど複数の繰り返し処理があります。どれが正解かは場面によります。この記事では、初学者が迷いやすい使い分けを、現場の可読性とデバッグしやすさの観点で整理します。


Javaのコレクション処理では、同じ結果を複数の書き方で実現できます。
初学者が迷いやすいのは、for文が古くて、Streamが常に良い書き方に見えることです。
この記事のポイント
- 単純な繰り返しは拡張for文で十分なことが多い
- 抽出・変換・集計はStreamが読みやすい場合がある
- 途中に複雑な処理やデバッグが必要ならfor文が強い
- チームの読みやすさを優先して選ぶ
拡張for文は現場でも普通に使う
一覧を順番に処理するだけなら、拡張for文は今でも十分に読みやすいです。
for (Order order : orders) {
if (order.isExpired()) {
expiredOrders.add(order);
}
}条件分岐やログ出力、途中の変数確認が必要な処理では、for文の方が追いやすいことがあります。
Streamが向いている処理
Streamは、一覧から条件に合うものを取り出す、別の形に変換する、集計する、といった処理で読みやすくなります。
List<String> activeUserNames = users.stream()
.filter(User::isActive)
.map(User::getName)
.toList();このように、処理の流れが「絞る、取り出す、集める」と読めるならStreamは有効です。
読みにくいStreamは避ける
問題は、Streamを使うこと自体が目的になってしまうケースです。処理が長くなり、ラムダの中に条件分岐や副作用が増えると、かえって読みにくくなります。
避けたいStreamの例
- 1つのStreamチェーンが長すぎる
forEachの中で外部の変数を更新している- 例外処理やログ出力が混ざっている
- デバッグ時に中間値を追いにくい
現場での選び方
使い分けの目安
- 単純な繰り返しなら拡張for文
- 一覧の変換・抽出ならStream
- インデックスが必要なら通常のfor文
- 複雑な業務ロジックが入るなら無理にStream化しない
レビューでは「新しい書き方か」より、次に読む人が意図を追えるかが見られます。
現場ではデバッグしやすさも選定基準になる
たとえば、入金済みで期限が切れた注文のうち、10,000円以上を担当者の確認対象にします。同じ注文6件を入力順に処理すると、9,999円の注文61は外れ、10,000円の注文42は残ります。
| 注文ID | 入金・期限 | 金額 | 判定 |
|---|---|---|---|
| 42 | 入金済み・期限切れ | 10,000円 | 対象 |
| 72 | 未入金・期限切れ | 12,000円 | 未入金 |
| 81 | 入金済み・期限内 | 12,000円 | 期限内 |
| 61 | 入金済み・期限切れ | 9,999円 | 10,000円未満 |
| 14 | 入金済み・期限切れ | 12,000円 | 対象 |
| 95 | 未入金・期限内 | 5,000円 | 未入金 |
対象一覧だけが必要なら、Streamでは3つの条件を上から読み、eligibleOrders(orders).toList()で結果を集められます。次は配布ファイルのメソッド抜粋です。
static Stream<Order> eligibleOrders(List<Order> orders) {
return orders.stream()
.filter(Order::paid)
.filter(Order::expired)
.filter(order -> order.amount() >= 10_000);
}「なぜこの注文が一覧に出なかったか」も調べたいなら、for文の各分岐で、注文IDと最初に満たさなかった条件を記録できます。注文95は期限内で金額も足りませんが、この順で確認すると最初の除外理由は未入金です。次は同じファイルのメソッド抜粋です。
static Selection selectWithFor(List<Order> orders) {
List<Order> targets = new ArrayList<>();
List<Exclusion> exclusions = new ArrayList<>();
for (Order order : orders) {
if (!order.paid()) {
exclusions.add(new Exclusion(order.id(), "未入金"));
continue;
}
if (!order.expired()) {
exclusions.add(new Exclusion(order.id(), "期限内"));
continue;
}
if (order.amount() < 10_000) {
exclusions.add(new Exclusion(order.id(), "10,000円未満"));
continue;
}
targets.add(order);
}
return new Selection(targets, exclusions);
}どちらも対象は注文42、14の順です。結果に後から追加する仕様がある場合は、Stream.toList()の結果は変更不可です。Streamでも追加できる一覧を返すなら、collect(Collectors.toCollection(ArrayList::new))で可変の一覧に集めます。
同じ注文6件を実行して比べる
ZIPにOrderFilterDemo.javaの全文と手順書を収録しています。展開先でJDK 17を使い、java OrderFilterDemo.javaを実行すると、対象の順序と除外理由を確認できます。
レビューではチームの読みやすさが見られる
新人がやりがちなのは、Streamを覚えたばかりで何でもStream化することです。レビューでは、チーム全員が読めるか、既存コードの書き方と大きくズレていないかも見られます。
// レビューコメント例
// この処理は条件分岐が多く、今後ログ出力も入りそうです。
// Streamで1行に寄せるより、for文で条件ごとに分けた方が保守しやすそうです。for文とStreamの判断基準
- 抽出・変換・集計が素直に読めるならStream
- 途中でログやデバッグが必要ならfor文
- 条件分岐が増えてきたら無理にStream化しない
- 既存コードの書き方とチームの慣れも見る
まとめ
for文とStreamは優劣ではなく使い分けです。初現場では、短さよりも読みやすさとデバッグしやすさを基準に選びましょう。
