Java Tips | 基本ユーティリティ:toString生成

Java Java
スポンサーリンク

toString は「オブジェクトの名刺」を作るメソッド

toString() は、オブジェクトを人間が読める文字列に変換するためのメソッドです。
業務システムでは、ログ出力、デバッグ、エラー調査、監視など、あらゆる場面でこの「文字列表現」が使われます。

ここが手抜きになっていると、ログに「com.example.User@5f184fc6」のような意味不明な文字列しか出ず、障害対応のときに地獄を見ます。
逆に、きちんと設計された toString() があると、「この時点でこのオブジェクトはこういう状態だった」と一目で分かり、調査コストが劇的に下がります。

toString() は、単なるおまけではなく、「オブジェクトの名刺」をどうデザインするか、という設計の一部だと捉えてほしいです。


デフォルトの toString と、なぜそれでは足りないのか

Object.toString のデフォルト実装

何もオーバーライドしない場合、toString()Object クラスのデフォルト実装が呼ばれます。
その形式はだいたい「クラス名@ハッシュコード(16進数)」です。

public class User {
    private String id;
    private String name;
}

User u = new User();
System.out.println(u.toString());  // 例: com.example.User@5f184fc6
Java

この情報から分かるのは、「どのクラスのインスタンスか」と「ハッシュコード」だけです。
業務で知りたい「ID は何か」「状態はどうか」といった情報は一切分かりません。

デバッグ中にこの表示を見て、「中身が見えない…」と感じたことがあるなら、それは toString() がまだ「名刺」として機能していない状態です。

業務で欲しいのは「中身が分かる toString」

業務システムで本当に欲しいのは、「このオブジェクトの重要なフィールドが、ひと目で分かる文字列表現」です。
例えば User なら、「id」「name」「status」などが分かれば、ログを見ただけで状況をかなり再現できます。

だからこそ、業務で使うドメインクラスや DTO には、意識的に toString() を実装していく価値があります。


手書きでの toString 実装の基本

シンプルな toString の例

まずは、典型的な手書きの toString() を見てみましょう。

public class User {

    private final String id;
    private final String name;
    private final int age;

    public User(String id, String name, int age) {
        this.id  = id;
        this.name = name;
        this.age  = age;
    }

    @Override
    public String toString() {
        return "User{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", age=" + age +
               '}';
    }
}
Java

出力はこんな感じになります。

User user = new User("u001", "Taro", 20);
System.out.println(user);
// User{id='u001', name='Taro', age=20}
Java

この形式の良いところは、クラス名とフィールド名が明示されていて、ログを見た人がすぐに意味を理解できることです。
「どのフィールドがどの値か」が一目瞭然なので、障害調査のときに非常に役立ちます。

StringBuilder を使った書き方

文字列連結が多くなる場合は、StringBuilder を使っても構いません。

@Override
public String toString() {
    StringBuilder sb = new StringBuilder("User{");
    sb.append("id='").append(id).append('\'');
    sb.append(", name='").append(name).append('\'');
    sb.append(", age=").append(age);
    sb.append('}');
    return sb.toString();
}
Java

最近の Java コンパイラは、単純な + 連結を内部で StringBuilder に変換してくれるので、パフォーマンスをそこまで気にしないなら、素直に + で書いても問題ありません。
大事なのは、「人間が読んで分かりやすい形になっているか」です。


Objects.toString と null セーフな表現

Objects.toString の基本

java.util.Objects には、Objects.toString(Object o) というユーティリティがあります。
これは、「o が null なら "null"、そうでなければ o.toString()」という挙動をします。

String s = null;
System.out.println(Objects.toString(s));  // "null"
Java

さらに、第二引数にデフォルト値を渡せるオーバーロードもあります。

String s = null;
System.out.println(Objects.toString(s, ""));  // ""
Java

これを使うと、「null のときは空文字にしたい」「特定の文字にしたい」といった要件を簡単に表現できます。

toString 実装の中での活用

toString() の中で、フィールドが null になる可能性がある場合、Objects.toString を使うと安全で読みやすくなります。

@Override
public String toString() {
    return "User{" +
           "id='" + Objects.toString(id, "") + '\'' +
           ", name='" + Objects.toString(name, "") + '\'' +
           ", age=" + age +
           '}';
}
Java

こうしておけば、idname が null でも NullPointerException にはならず、空文字として出力されます。
「null をどう表現するか」は業務要件次第ですが、Objects.toString を使うと、そのポリシーをコードに落とし込みやすくなります。


StringJoiner や String.format を使った柔らかい書き方

StringJoiner でフィールドをつなぐ

複数のフィールドを「区切り文字付きでつなぐ」場合、StringJoiner を使うと意図が分かりやすくなります。

import java.util.StringJoiner;

@Override
public String toString() {
    StringJoiner sj = new StringJoiner(", ", "User{", "}");
    sj.add("id='" + id + '\'');
    sj.add("name='" + name + '\'');
    sj.add("age=" + age);
    return sj.toString();
}
Java

この書き方だと、「区切りは ,」「前後に User{} を付ける」という構造がはっきり見えます。
フィールドが増えても、sj.add(...) を足していくだけなので、メンテナンスもしやすいです。

String.format でテンプレート的に書く

String.format を使うと、「テンプレート+値」という形で書けます。

@Override
public String toString() {
    return String.format("User{id='%s', name='%s', age=%d}", id, name, age);
}
Java

テンプレート文字列を見れば、出力形式が一目で分かるのが利点です。
ただし、String.format は内部でフォーマッタを使う分、単純な連結より少し重いので、超大量に呼ばれるような場面では注意が必要です。
業務システムの普通のログ出力レベルなら、まず問題になりません。


実務で意識したい toString の設計ポイント

「何を出すか」をちゃんと選ぶ

toString に何でもかんでも全部出せばいい、というわけではありません。
業務では、次のような観点で「出すべきもの」「出すべきでないもの」を選ぶ必要があります。

業務的にそのオブジェクトを識別するのに必要な情報(ID、コード、状態など)は積極的に出す。
ログに残ると困る情報(パスワード、クレジットカード番号、個人情報の詳細など)は絶対に出さない。
巨大なコレクションや長大なテキストは、そのまま全部出すとログが読めなくなるので、件数や一部だけにとどめる。

例えば、ユーザーのパスワードを持つクラスで、うっかりパスワードまで toString() に含めてしまうと、ログに平文パスワードが残るという最悪の事態になります。
ここは「便利さ」よりも「セキュリティ」を優先すべきポイントです。

ネストしたオブジェクトの扱い

オブジェクトが別のオブジェクトを持っている場合、その toString() をそのまま呼ぶかどうかも設計ポイントです。

例えば、OrderUser を持っているとします。

public class Order {
    private String orderId;
    private User user;
    // ...
}
Java

Order.toString() の中で user.toString() をそのまま呼ぶと、User の全情報が展開されます。
それが望ましい場合もあれば、「Order のログにはユーザー ID だけでいい」という場合もあります。

その場合は、user.toString() ではなく、user.getId() だけを出すようにします。

@Override
public String toString() {
    return "Order{" +
           "orderId='" + orderId + '\'' +
           ", userId='" + (user != null ? user.getId() : null) + '\'' +
           '}';
}
Java

こうすることで、「Order のログは Order に関する情報だけ」「User の詳細は User のログで見る」という分離ができます。


IDE やライブラリに toString 生成を任せる現実的な方法

IDE の自動生成機能

IntelliJ IDEA や Eclipse には、toString() を自動生成する機能があります。
フィールドを選ぶと、さきほどのような「クラス名{フィールド=値,…}」形式のメソッドを一瞬で作ってくれます。

生成されたコードをそのまま使ってもいいですが、「どのフィールドを含めるか」は自分で選ぶようにしてください。
特に、パスワードやトークンなどの機密情報は、生成対象から外すのが鉄則です。

自動生成は「手を動かす部分の省力化」であって、「何を出すかの判断」を代わりにしてくれるわけではありません。
そこを勘違いしないことが、業務での安全な運用につながります。

Lombok やレコードの活用

Lombok を使っているプロジェクトなら、@ToString アノテーションで toString() を自動生成できます。
excludeof などの属性で、含めるフィールド・含めないフィールドを制御できます。

Java 16 以降の record を使う場合は、コンパイラが自動的にそれなりに読みやすい toString() を生成してくれます。
レコードは「データキャリア」として設計されているので、toString() もその用途に合った形になっています。

どちらの場合も、「機密情報を含めない」「巨大なフィールドをそのまま出さない」といったポリシーは、アノテーションの設定や設計でコントロールする必要があります。


まとめ:初心者が toString 生成で身につけるべき感覚

toString() は、単に「文字列にするメソッド」ではなく、「このオブジェクトをどう説明するか」を決める設計の一部です。
業務で本当に役に立つのは、「クラス名と、業務的に重要なフィールドがコンパクトにまとまっている toString」です。

デフォルト実装に任せず、自分で「名刺」をデザインする。
null や機密情報、巨大なデータの扱いを意識する。
IDE やライブラリの自動生成は使いつつも、「何を出すか」の判断は自分で握る。

この感覚が身につくと、ログやデバッグのストレスが一気に減ります。

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