forEach とラムダの基本 — 可読性向上
ラムダは「短く書ける無名の関数」、forEachは「コレクションの各要素に処理を当てる」ためのメソッドです。従来の for 文より読みやすく、意図が伝わりやすいコードになります。初心者向けに、List/Set/Map の定番パターンと注意点をまとめます。
基本の書き方と考え方
List<String> list = List.of("a", "b", "c");
// ラムダ式
list.forEach(s -> System.out.println(s));
// メソッド参照(同じ意味)
list.forEach(System.out::println);
Java- ラムダ式: 引数 -> 処理。引数が1つなら括弧省略可、処理が1行なら波括弧・return省略可。
- メソッド参照: 既存メソッドをそのまま渡す書き方(System.out::println など)。
- forEachの受け取り: 引数は「Consumer<T>」=Tを受け取り、戻り値なしの処理。
List と Set の forEach
全件処理(出力・集計)
List<Integer> nums = List.of(10, 20, 30);
nums.forEach(n -> System.out.println(n));
int[] sum = {0}; // ラムダ内で更新するための箱(後述)
nums.forEach(n -> sum[0] += n);
System.out.println(sum[0]); // 60
Java- 読みやすさ: “各要素に対してこの処理をする”が直感的。
- 可変の外部状態: ラムダ内でローカル変数は再代入不可。配列やAtomic型を使うと更新できる。
条件付き処理
Set<String> names = Set.of("Sato", "Tanaka", "Aoi");
names.forEach(n -> {
if (n.startsWith("T")) System.out.println("T: " + n);
});
Java- 複数行: 波括弧でブロックにして必要な処理を並べる。
Map の forEach(キーと値の同時処理)
Map<String, Integer> prices = Map.of("apple", 100, "banana", 200);
// エントリ単位
prices.forEach((k, v) -> System.out.println(k + "=" + v));
// 条件付き(値に応じて)
prices.forEach((k, v) -> {
if (v >= 150) System.out.println("高い: " + k);
});
Java- 引数が2つ: Map.forEachは BiConsumer<K,V> を受け取るため、(k, v) -> … の形。
よく使うミニレシピ
- フィルタ+処理(簡易版):
List<String> items = List.of("A", "BB", "CCC");
items.forEach(s -> { if (s.length() >= 2) System.out.println(s); });
Java- インデックスが必要なときは for 文に切り替え:
List<String> items = List.of("A", "B", "C");
for (int i = 0; i < items.size(); i++) {
System.out.println(i + ":" + items.get(i));
}
Java- メソッド参照で簡潔に:
List<String> logs = List.of("ok", "warn", "error");
logs.forEach(System.out::println);
Java注意点と落とし穴
- 例外処理: ラムダ内で発生する例外は自分で try/catch する。
list.forEach(s -> {
try {
doSomething(s);
} catch (Exception e) {
System.err.println("失敗: " + s);
}
});
Java- break/continueがない: forEachは「途中で抜ける・スキップする」構文がない。必要なら通常の for/while へ。
- 代替: 条件で早期 return(ラムダ1回分の終了)や、ストリームの anyMatch/findFirst を使う。
- 外部状態の更新に不向き: 合計やカウントを外部変数に溜めるより、Streamのsum/countなどを検討。どうしても必要なら配列・AtomicIntegerに格納。
- 可変操作とConcurrentModificationException: forEach中に同じコレクションを構造的に変更(add/remove)すると例外。Iteratorのremoveや変更用に別リストへ集める。
例題で身につける(解説つき)
例題1: 単純出力を短くする
List<String> tasks = List.of("起床", "朝食", "出勤");
tasks.forEach(System.out::println);
Java- ポイント: メソッド参照で読みやすさと短さを両立。
例題2: 条件付きでログ出力
List<Integer> codes = List.of(200, 404, 500);
codes.forEach(c -> {
if (c >= 400) System.out.println("[ERROR] code=" + c);
});
Java- ポイント: “全件+条件分岐”が自然に書ける。
例題3: Mapでテンプレート化
Map<String, Integer> stock = Map.of("A", 0, "B", 3, "C", 5);
stock.forEach((id, qty) -> {
String msg = String.format("ID=%s, qty=%d", id, qty);
System.out.println(msg);
});
Java- ポイント: キー・値を同時に使った整形が簡潔。
例題4: 例外を握りつぶさず記録
List<String> files = List.of("a.txt", "b.txt");
files.forEach(f -> {
try {
readFile(f);
} catch (IOException e) {
System.err.println("[WARN] 読み込み失敗: " + f + " " + e.getMessage());
}
});
Java- ポイント: ラムダ内での try/catch。forEach全体は止めず、個別に処理。
テンプレート集(すぐ使える雛形)
- List/Set 用:
collection.forEach(x -> {
// x を使った処理
});
Java- Map 用(キー・値):
map.forEach((k, v) -> {
// k と v を使った処理
});
Java- メソッド参照(1引数・戻りなし):
collection.forEach(ClassName::methodName);
Java- 安全な例外処理込み:
list.forEach(x -> {
try { process(x); }
catch (Exception e) { handle(e, x); }
});
Java使い分けの目安
- forEach(ラムダ): 全件に単純処理、インデックス不要、途中終了しない、可読性重視。
- 従来の for/while: 途中で抜ける・スキップ、インデックスが必要、複雑な制御がある。
- Stream API: 集計・変換・フィルタを「宣言的」に書きたいとき。(sum, filter, map, collect など)
