peek の使い方(デバッグ) — 中間ログ
ストリームの「途中」を覗いて、要素がどう変わっているかを確認するのが peek。中間操作としてログを差し挟めるので、フィルタや変換の結果を安全に観察できます。実務では「疑わしい箇所の直後」に入れて、終端操作で流し切るのがコツです。
基本のポイント(なぜ peek が便利か)
- 役割: 中間操作で、要素をそのまま流しつつ副作用(ログ)だけ行う。要素は変更しない。
- 遅延評価: 終端操作(collect/sum/forEach など)が無ければ実行されない。ログが出ないときは終端操作の不足が原因。
- 使いどころ: filter の直後、map の直後、distinct/sorted 前後など「要素の形や数が変わる箇所」に挟む。
基本コード例(すぐ試せる)
import java.util.*;
import java.util.stream.*;
public class PeekBasics {
public static void main(String[] args) {
List<String> words = List.of("apple","banana","cherry");
List<String> out = words.stream()
.peek(w -> System.out.println("[start] " + w)) // 入力確認
.filter(w -> w.length() >= 6)
.peek(w -> System.out.println("[after filter] " + w)) // 絞り込み後
.map(String::toUpperCase)
.peek(w -> System.out.println("[after map] " + w)) // 変換後
.collect(Collectors.toList()); // 終端(これがないと動かない)
System.out.println(out); // [BANANA, CHERRY]
}
}
Java- ラベル: どの段階で要素が残り、どう変わるかが一目で分かる。
例題で理解する(デバッグの実戦)
例題1: 数値処理の段階確認
int sum = IntStream.rangeClosed(1, 10)
.peek(n -> System.out.println("in: " + n))
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println("even: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("square: " + n))
.sum(); // 終端
System.out.println("sum=" + sum);
Java- ポイント: フィルタで減り、map で値が変わる様子を逐次確認。
例題2: テキスト整形の誤変換を追う
List<String> names = List.of(" Alice ", "Bob", "", " ");
List<String> cleaned = names.stream()
.map(String::trim)
.peek(s -> System.out.println("trim: '" + s + "'"))
.filter(s -> !s.isEmpty())
.peek(s -> System.out.println("non-empty: '" + s + "'"))
.map(String::toUpperCase)
.peek(s -> System.out.println("upper: '" + s + "'"))
.toList();
System.out.println(cleaned); // [ALICE, BOB]
Java- ポイント: 空文字の扱いなど「境界ケース」を目で追える。
例題3: 重複排除・ソートの前後比較
List<String> items = List.of("b","a","b","c");
List<String> result = items.stream()
.peek(s -> System.out.println("start: " + s))
.distinct()
.peek(s -> System.out.println("distinct: " + s))
.sorted()
.peek(s -> System.out.println("sorted: " + s))
.toList();
System.out.println(result); // [a, b, c]
Java- ポイント: distinct と sorted の効果を段階で把握。
テンプレート集(そのまま使える)
- 段階ログ(汎用)
stream
.peek(x -> System.out.println("start: " + x))
.filter(this::cond)
.peek(x -> System.out.println("after filter: " + x))
.map(this::transform)
.peek(x -> System.out.println("after map: " + x))
.collect(Collectors.toList());
Java- ラベル付きログ(関数化)
static <T> java.util.function.Consumer<T> log(String label) {
return x -> System.out.println(label + ": " + x);
}
// 使い方
stream.peek(log("start")).filter(...).peek(log("after filter")).collect(...);
Java- プリミティブストリーム
int s = IntStream.rangeClosed(1, 5)
.peek(n -> System.out.println("n=" + n))
.sum();
Java- 条件付きで詳細ログ(重いときは抑制)
boolean debug = true;
stream.peek(x -> { if (debug) System.out.println(x); }).collect(Collectors.toList());
Java落とし穴と回避策(本番で困らないために)
- 終端操作が必須: peek は中間操作。collect/sum/forEach などの終端がないと何も起きない。
- 本番での大量標準出力: 標準出力は遅い。ログフレームワーク(レベル制御)を使うか、フラグで抑制する。
- 副作用は最小限: peek は「観察用」。本番ロジック(更新・外部書き込み)は forEach など終端で行う。
- 並列ストリームの順序: parallel + peek は表示順が前後する。順序が必要なら
forEachOrderedで出力を行うか、直列にする。 - 機密情報の漏えい: デバッグログに個人情報や秘密情報を出さない。マスキングや出力抑制を徹底。
まとめ
peekは「中間で覗く」ためのデバッグ専用ツール。フィルタや変換の直後に置くと効果的。- 終端操作がないと動かない点、並列で順序が乱れる点に注意。ログは軽く・制御可能に。
- 迷ったら「疑わしい処理の直後に peek を挟む → 終端で流し切る」で、原因箇所を素早く特定できます。
