Java 逆引き集 | toString の実装方針 — ログ/デバッグの可視化

Java Java
スポンサーリンク

toString の実装方針 — ログ/デバッグの可視化

toString は「オブジェクトの状態を文字列で見える化」するための最重要メソッド。
ログやデバッグで素早く状況把握できるよう、読みやすく・安全に・一貫した書式で実装するのが基本です。


基本原則(まずはここから)

  • 読みやすい構造: クラス名 + フィールド名=値 をカンマ区切りで。例: User{id=1, name=Tanaka}
  • 簡潔で十分: 追跡に必要なフィールドのみ。巨大・機密データは避ける。
  • null 安全: null の場合はそのまま “null” を出す、または空文字扱いに統一。
  • 副作用なし: 計算・I/O をしない。単なる文字列生成に徹する。
  • 一貫性: プロジェクト全体で同じ書式ルールを採用(出力の比較がしやすい)。

使い分けの考え方(「出す」「隠す」「短くする」)

  • 必須情報だけ出す: ID、種類、主要な属性。
  • 機密情報は隠す: パスワード、トークン、個人情報はマスクまたは除外。
  • 長いデータは省略: 長文や配列は件数・先頭数件だけ。

例題で身につける

例題1: 値オブジェクトの基本形

import java.util.Objects;

public final class User {
    private final int id;
    private final String name;
    private final String email; // これは表示したいが…

    @Override
    public String toString() {
        return "User{" +
               "id=" + id +
               ", name=" + name +
               ", email=" + maskEmail(email) +
               '}';
    }

    private static String maskEmail(String s) {
        if (s == null) return "null";
        int at = s.indexOf('@');
        if (at <= 1) return "***";
        return s.substring(0, 1) + "***" + s.substring(at);
    }
}
Java
  • ポイント: フィールド名=値の形式、機密の最小マスク。

例題2: コレクションと配列は「サイズ+一部のみ」

import java.util.List;

public final class Order {
    private final String id;
    private final List<String> items; // 件数が多い可能性

    @Override
    public String toString() {
        return "Order{" +
               "id=" + id +
               ", itemsCount=" + (items == null ? 0 : items.size()) +
               ", itemsPreview=" + preview(items, 3) +
               '}';
    }

    private static <T> String preview(List<T> list, int limit) {
        if (list == null || list.isEmpty()) return "[]";
        int n = Math.min(list.size(), limit);
        return list.subList(0, n).toString() + (list.size() > n ? "..." : "");
    }
}
Java
  • ポイント: 速度・ログ量・可読性のバランスを取る。

例題3: ネスト構造と循環参照対策

public final class Node {
    private final String name;
    private Node parent; // 参照が循環しうる

    @Override
    public String toString() {
        // parent は名前だけにして、全体を辿らない
        return "Node{" +
               "name=" + name +
               ", parent=" + (parent == null ? "null" : parent.name) +
               '}';
    }
}
Java
  • ポイント: toString で全体を辿らない。無限再帰を避ける。

実装テンプレート集

  • 基本形(安全・簡潔)
@Override
public String toString() {
    return "ClassName{" +
           "field1=" + field1 +
           ", field2=" + field2 +
           '}';
}
Java
  • 機密のマスキング
private static String mask(String s) {
    if (s == null) return "null";
    return s.length() <= 4 ? "****" : s.substring(0, 2) + "****" + s.substring(s.length() - 2);
}
Java
  • 配列のサマリ
private static String summarize(int[] arr, int limit) {
    if (arr == null) return "null";
    int n = Math.min(arr.length, limit);
    StringBuilder sb = new StringBuilder("[");
    for (int i = 0; i < n; i++) {
        if (i > 0) sb.append(", ");
        sb.append(arr[i]);
    }
    sb.append(arr.length > n ? ", ..." : "");
    sb.append("]");
    return sb.toString();
}
Java

ライブラリや言語機能で楽をする

  • IDE自動生成: IntelliJ/ Eclipse の「Generate toString」で統一書式。
  • Lombok @ToString: フィールド指定、exclude、callSuper など柔軟。
  • Apache Commons Lang ToStringBuilder: 反射版や短縮版で一括生成。
  • record(Java 16+): デフォルトの toString が読みやすいが、機密が含まれる場合は上書き推奨。

ログとの連携のコツ

  • 遅延評価: ログはプレースホルダを使う(logger.info(“user={}”, user))。
  • 量を制御: INFO ではサマリ、DEBUG では詳細。
  • 一貫したフォーマット: クラス名{key=value,…} を全体で統一。検索が楽になる。

よくある落とし穴と回避策

  • 巨大データの全出力: ログ爆増・性能劣化。→ サマリ化(件数、先頭数件)。
  • 循環参照で再帰: StackOverflow。→ 辿る深さを制限/キー情報のみ表示。
  • 機密情報の垂れ流し: 監査・漏洩リスク。→ マスキング or 非表示。
  • 例外発生・重い計算: toString は軽量・例外なしで。→ 純粋な文字列構築だけにする。

まとめ

  • 目的は「ログ/デバッグで即状況が分かる」こと。簡潔・安全・一貫性が鍵。
  • 重要フィールドだけを、クラス名{key=value}で出す。機密や巨大データはサマリ化・マスク。
  • ライブラリや自動生成を活用しつつ、循環参照や性能に配慮した実装を選ぶ。

タイトルとURLをコピーしました