Java 逆引き集 | peek の使い方(デバッグ) — 中間ログ

Java Java
スポンサーリンク

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 を挟む → 終端で流し切る」で、原因箇所を素早く特定できます。
タイトルとURLをコピーしました