この記事は、ラクス Advent Calendar 2024の4日目の記事です。昨日は、@R_1112 の単体テストの構造パターンの記事でした。
JavaのtoString()で「ちょっと怖いな」という使い方をしているケース見かけたので注意喚起として書いておきます。
toString()とは
toString()はObjectクラスで定義されているメソッドです。Objectクラスは全てのクラスのスーパークラスなので、全てのクラスでtoString()はOverrideできます。
デフォルトでは「クラス名@ハッシュ値
」となっていますが、一般的には以下のように各フィールドの値をそのまま出力する形でOverrideすることが多いと思います。
public class Name {
public String lastName;
public String firstName;
略
@Override
public String toString() {
// 例: lastName=田中, firsName=太郎
return "lastName=" + lastName + ", firstName=" + firstName;
}
}
public class Money {
public BigDecimal value;
略
@Override
public String toString() {
// 例: 12345.6789
return value.toPlainString()
}
}
toString()はさまざまなライブラリから呼び出される
toString()は明示的に呼び出すこともできますが、標準ライブラリやその他のライブラリの内部で呼ばれるケースがあります。
例えば、System.out.println()
の内部でもtoString()が呼ばれており、任意のクラスを引数に取ってもよしなに文字列表示してくれるようになっています。
Name name = new Name("田中", "太郎");
System.out.println(name); // lastName=田中, firsName=太郎
Money money = new Money(new BigDecimal("12345.6789"));
System.out.println(money); // 12345.6789
他にも各種ログライブラリ1やThymeleaf、Apache Commons CSVのような、主要ライブラリも様々なクラスをtoString()で一律文字列出力してくれます。
printer.printRecord(name, money); // "lastName=田中, firsName=太郎","12345.6789"
toString()をOverrideすべきか?
さまざまなライブラリがよしなにtoString()を読んでくれるので「toString()をOverrideすればいいじゃん」となりがちですが、それは本当にtoString()をOverrideすべきなのでしょうか?以下にいくつか微妙な例を挙げていきます
1. toString()に外部仕様が混ざる
Apache Commons CSVの例のようにCSV出力したいケースを考えてみましょう。
たとえば、Name
クラスの場合、CSVでは姓名を結合した「田中 太郎
」や「田中 太郎
」形式で出力したくなりそうです。同様に、Money
クラスでは金額に通貨単位の付与やカンマ区切りをして「12,345.68円
」のようにフォーマットを整えて出力したくなりそうです。
toString()はメソッド名通りただ文字列化するだけのメソッドのはずなので、流石にやりすぎですよね。2
文字列化以外の処理をする場合は、専用の別メソッドを用意して呼び出すようにしましょう。
printer.printRecord(name.toFullName(), money.toFormat(formatter)); // "田中太郎","12,345円"
2. ログに必要な情報が取得できない/出してはいけない情報が出る
先に記載したようにtoString()はログ出力時にも呼び出されます。
アプリの用途に合わせて過度に情報を整形してしまうと、ログの調査時に必要な情報が取得できず困ることにもつながります。例えばNameを姓名繋ぎで出力していた場合、どこまが姓でどこからが名なのかわかりません。姓名ならだいたい推測もつくと思いますが、もっと複雑なクラスの場合、出力されているのが何の項目かわからないと困ります。
また、シンプルなtoString()だったとしても困るケースがあります。それはクレジットカード情報やパスワードなどの機微な情報の時です。機微な情報はログ出力すべきでないですが、ライブラリなどで勝手にログ出力されてしまい、セキュリティ問題になる可能性があります。
3. 影響範囲調査が難しい
みなさん実装を修正する際はgrepやIDEの機能でクラスやメソッドの利用個所を調査すると思いますが、toString()の調査は難しいです。
他のクラスでOverrideされているtoString()がノイズになったり、先に記載したようにライブラリがよしなに使っている場合は特定不能です。
自動テストを書いていればテストの失敗などで気づけるケースはあると思いますが、自前で作成したメソッドであれば影響は限定的なので調査は楽になります。
まとめ
- toString()はメソッド名の通り、オブジェクトを文字列化するだけのメソッド
- toString()は意図せず呼びだされる可能性があるので、文字列化する内容は慎重に
- 意味ある文字列を出力する場合は、専用メソッドなどで自前で文字列化する
-
ログライブラリについての別の方の記事 https://zenn.dev/yyamada12/articles/860701683ada37 ↩
-
Nameの姓名つなぎはギリ許容ラインかもしれない ↩