【Effective Java】項目10:toString を常にオーバーライドする
toString をオーバライドして意味のある文字列を返すようにする。
toString のオーバーライド
java.lang.Object は toString() を実装しているが、ユーザーにとってよい情報を返すわけではない。
Object obj = new Object(); // 「クラス名@ハッシュコード値」の形式となる obj.toString // => java.lang.Object@549d1e83
equals や hashCode の一般契約ほどの重要性はないが、toString() はそのクラスを使いやすくする。
例えば、以下のように PhoneNumber の toString() を実装する。
@Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); }
こうすると、HashMap をデバッガで表示したときの情報がかなり見やすくなる。
PhoneNumber pn = new PhoneNumber(010, 999, 9999) pn.toString(); // => "(010) 999-9999 Map<String, PhoneNumber> map = new HashMap<String, PhoneNumber>(); map.put("Jenny", pn); // デバッガで map を表示した場合 => {Jenny=(010)999-9999}
実用的な範囲内で、toString メソッドは、オブジェクトに含まれている興味がある情報をすべて返すべきである。 ただし、オブジェクトが大きすぎたり、オブジェクトが文字列で表現するには向いてない状態を含む場合、適切な要約を返すべきである。
ドキュメンテーション
toString を実装する場合に重要な選択の一つは、toString の戻り値を明示的に定義するかどうかである。
値クラスに対しては toString() の返り値を明示的に定義することが推奨される。
toString() を明示的に定義し、String を引数とする static ファクトリーメソッドやコンストラクタを提供すると、文字列表現とオブジェクトの相互変換が可能になる。 Java ライブラリの多くの値クラスではこれが採用されており、BigInteger や BigDecimal、基本データ型などがそれにあたる。
一方、戻り値を定義してしまうことの欠点は、プログラマがその表現形式に依存したプログラムを書いてしまうことである。 これによって将来のリリースでは toString の形式を変えることができなくなる。
ただ、戻り値の厳密な値を明記するかどうかに関わらず、toString() 実装の意図はドキュメントとして明記したほうがよい。 なぜなら、戻り値を変更することを意図していた場合でも、ドキュメントとして明記しないとプログラマは toString() の値に依存したプログラムを書いてしまうからである。
アクセッサとしての toString()
形式を明示するかどうかに関わらず、toString() で返される情報へのアクセッサは必ず提供するべきである。
toString() で返す情報へのアクセッサを提供しない場合、プログラマは文字列を解析し、その値を取り出そうと試みる。 アクセッサを提供しなくても、toString で返される文字列が実質的なアクセッサになってしまうのである。
感想
toString() がアクセッサになってしまうという話は目から鱗だなーと思ったが「あーたしかにあるあるだな」と妙に納得した。
私の経験から言えば、API ユーザーは本当に創造的だということです。何かを誤って使用する方法が存在すれば、ユーザーは誤って使用します。
これは Amazon.co.jp: APIデザインの極意 Java/NetBeansアーキテクト探究ノート: Jaroslav Tulach, 柴田 芳樹: 本 からの引用。
胸に手を当てて考えると、自分が API 利用者の時はついつい無茶した使い方をしたくなる(よくないと分かっていても)。 「この情報がほしいので API に加えてください」とかリクエストするのめんどくさいんだよね。 最近はエンジニアとして多少成長したので、めんどくさがらずこういうリクエストもちゃんと出すようになったけど。 中・長期的に見れば無茶するとデメリットが大きく、それは技術的負債を貯めることになるから(技術的負債って言ってみたかった)