toString のオーバーライド
toString のオーバーライド とは
Object クラスの toString をオーバーライドして、自作クラスの中身を読みやすい文字列で表現しよう。
toString のオーバーライド
ここまでで クラス フィールド コンストラクタ メソッド といったオブジェクト指向の基本部品をひととおり学んできました。今回はそれらを応用して、Java の世界の すべてのクラスの祖先 である Object クラスが持つ toString() メソッドを オーバーライド (override) する方法を学びます。その前提として、Java ではすべてのクラスが extends を明示しなくても自動的に Object クラスを 継承 しているという仕組みを合わせて押さえておきましょう。toString() は、自作クラスの中身をどう文字列で見せるかを決める、とても実用的なメソッドです。
Javaのすべてのクラスは、明示的に書かなくても自動的にjava.lang.Objectクラスを継承しています。だからStringでもIntegerでも、もちろん自分で作ったPointクラスでも、toString()というメソッドが必ず使えます。これはJavaの世界の共通言語のようなものです。
Object クラスと toString の関係
Object クラスは Java の世界のすべてのクラスの 親 です。Object には toString() equals() hashCode() といった、どのオブジェクトでも持っていてほしいメソッドがあらかじめ定義されています。何も書かなくても、自分で作ったクラスのインスタンスに対して obj.toString() が呼べるのはそのおかげです。
しかし、デフォルトの toString() はあまり親切な内容を返してくれません。試しに Point という座標を表すクラスを作って、デフォルトの toString() を呼んでみると、こんな結果になります。
Java
public class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
// 呼び出し側
Point p = new Point(3, 4);
System.out.println(p.toString());
// 出力例: Point@1540e19d出てきたのは Point@1540e19d という、ちょっと暗号めいた文字列です。これは「Point クラスの、ハッシュコード 1540e19d のインスタンス」という意味で、デバッグには使えなくはないものの、人間にとっては中身がさっぱりわからない表記です。
デフォルトの
toString()が返すClassName@hashCodeという形式は、Object.toString()の実装そのものです。クラスのフルネームと、@記号、そしてInteger.toHexString(hashCode())をくっつけた文字列を返す、という仕様になっています。
ここで登場するのが オーバーライド です。Point クラスの中で toString() を 上書き してしまえば、p.toString() の戻り値を Point(3, 4) のような読みやすい形に変えられます。これが今回のレッスンの主役です。
@Override アノテーションとオーバーライドの仕組み
親クラスのメソッドを子クラスで上書きすることを オーバーライド (override) と呼びます。Java でオーバーライドを書くときは、メソッドの上に @Override という アノテーション を付けるのが定番です。
Java
public class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}これで new Point(3, 4).toString() を呼ぶと、戻り値は Point(3, 4) という人間に優しい文字列になります。System.out.println(p) のように、toString() を明示的に書かなくても、println は内部で自動的に toString() を呼び出してくれるので、Point(3, 4) と表示されます。
@Override アノテーションは、なくてもコンパイル自体は通ります。しかし付けておくことで、いくつもの恩恵が得られます。
- メソッドシグネチャの間違いに気づける —
toStringをtostringと打ち間違えたら、コンパイラが「親クラスにそんなメソッドはない」とエラーで教えてくれる - 読み手に意図が伝わる — このメソッドはオーバーライドのつもりで書いた、と明示できる
- 将来の親クラス変更に強くなる — 親クラス側で
toString()の引数や戻り値が変わったとき、コンパイラがズレに気づける
@Overrideは付け忘れても動くので、最初のうちは省略しがちです。しかしJavaのチームでコードレビューを受ければ、ほぼ確実に「@Overrideを付けてください」とコメントされます。最初から付ける癖をつけておくと、後でバグに泣かされる確率がぐっと減ります。
ここで、Object クラスと Point クラスのオーバーライド関係を図で確認しておきましょう。Point は Object を extends しなくても自動的に継承しているという点が、Java のオブジェクトモデルの面白いところです。
図の <|-- は 継承 の関係を表します。Point は Object を extends するキーワードを書いていなくても、Java コンパイラが自動的にこの線を引いてくれます。そして toString() を再定義することで、Object の同名メソッドを 上書き している様子が見えるはずです。
オーバーライドする利点 ─ デバッグとログがぐっと楽になる
toString() をオーバーライドする最大のメリットは、デバッグ と ログ出力 がとても楽になることです。たとえば ArrayList<Point> の中身をログに出したいとき、toString() を書いていなければ次のような出力になります。
Java
List<Point> points = List.of(new Point(0, 0), new Point(3, 4));
System.out.println(points);
// 出力例: [Point@1540e19d, Point@677327b6]これでは座標がわかりません。一方、toString() をオーバーライドしておくと、List の toString() が中の各要素の toString() を呼び出してくれるので、次のような出力になります。
Java
List<Point> points = List.of(new Point(0, 0), new Point(3, 4));
System.out.println(points);
// 出力例: [Point(0, 0), Point(3, 4)]中身が一目でわかります。これがあるのとないのとでは、バグを追いかけるときの体感速度が 10 倍くらい変わります。
ログ出力やデバッガ表示だけでなく、
IDEのVariablesビューや、JUnitのテスト失敗メッセージなど、Javaの世界の至るところでtoString()の戻り値が表示されます。読みやすいtoString()を 1 つ書いておくだけで、開発体験全体がアップグレードされるイメージです。
それだけでなく、ログ集約サービスやエラー監視ツールにスタックトレースが送られたとき、そのオブジェクトの状態が toString() 経由で記録されることもよくあります。Point(3, 4) と記録されているのと、Point@1540e19d と記録されているのとでは、本番障害の調査スピードがまったく違うのです。
よくある間違い
toString() をオーバーライドするときに、初学者がよくつまずく落とし穴を 3 つ紹介します。
@Overrideアノテーションを忘れる — 付け忘れてもコンパイル自体は通ってしまうので、気づかずにメソッド名をtostringと書き間違えても、コンパイラは何も言ってくれません。するとtoString()は親の実装が呼ばれ続け、いつまで経ってもPoint@1540e19dのままで「あれ、書いたはずなのに反映されない」とハマります。@Overrideを付けておけば、コンパイル時に即エラーで教えてくれます- 引数を勝手に増やしてしまう —
Object.toString()は引数なしのメソッドです。これをpublic String toString(int n)のように引数付きにしてしまうと、それは別のメソッドのオーバーロード(overload) になり、オーバーライドにはなりません。@Overrideを付けていればコンパイラが「親クラスに引数intのtoStringはない」と教えてくれます - 戻り値の型を間違える —
Object.toString()の戻り値はStringです。これをvoidにしたり、Objectにしたりすると、String以外の戻り値を持つメソッドはオーバーライドとして認められません。やはり@Overrideを付けておけばコンパイラが守ってくれます
もうひとつ地味に多いのが、toString() の中で フィールド の値ではなくクラス名そのものを返してしまう間違いです。return "Point"; と書くと、確かに動きはしますが、x と y の中身が一切わからないので意味がありません。せっかくオーバーライドするなら、Point(" + x + ", " + y + ")" のように フィールド の値も組み込んで、インスタンスの状態 を表現する文字列にしましょう。
@Overrideがあなたを 3 つのバグから救うというのは大げさではありません。アノテーション 1 行付けるだけで、メソッド名のタイポ、引数違い、戻り値違いの 3 種類の事故をコンパイル時に防げます。ノーコストで効く静的解析だと思って、必ず付ける習慣にしましょう。
やってみよう
それでは課題に挑戦しましょう。本レッスンでは Solution.pointToString(int x, int y) というメソッドを通じて、Point クラスの toString() がどんな文字列を返すかをシミュレートしてもらいます。
手順は次のとおりです。
- 右側のエディタを開く
Solutionクラスの中にpublic static String pointToString(int x, int y)メソッドが用意されている- メソッドの本体で
"Point(" + x + ", " + y + ")"という文字列を組み立ててreturnする - 「実行」ボタンを押して、テストが緑になることを確認する
テストは 3 つあります。pointToString(3, 4) が "Point(3, 4)" を返すこと、pointToString(0, 0) が "Point(0, 0)" を返すこと、pointToString(-1, -2) が "Point(-1, -2)" を返すこと、の 3 つです。マイナスの値もそのまま Java の + 演算で文字列に変換されるので、特別な処理を書く必要はありません。
もし , の後ろの半角スペースを忘れたり、括弧の種類を [] や {} にしてしまったりすると、テストは fail します。Point(3, 4) の正確な形をしっかり目で確認しながら書いてみてください。
慣れてきたら、エディタ上に本物の Point クラスを書き起こして、@Override 付きの toString() を実装し、new Point(3, 4).toString() を呼び出して文字列を作る、という形にチャレンジしてみるのもおすすめです。今回の課題はその 練習版 だと思って、まずは文字列を組み立てる感覚をつかみましょう。
toString() は Java で自作クラスを作るたびに、ほぼ必ず書きたくなる定番メソッドです。ここでオーバーライドの感覚をしっかり身につけておくと、これから先 equals() や hashCode() といった他の Object 系メソッドのオーバーライドにもスムーズに進めます。
よくある質問
Q. Java と他言語の文法はどう違いますか?
A. Java は静的型付けで、変数宣言時に型を明示します(int x = 0;)。中括弧でブロックを表し、文末にセミコロンが必要です。Python の動的型付けや JavaScript の柔軟さに慣れていると最初は窮屈ですが、コンパイル時にバグが発見できる安全性がメリットです。
Q. コードが動かないときに最初に見るべき場所は?
A. コンパイルエラーは行番号とエラー種別(cannot find symbol、incompatible types など)が表示されます。実行時例外はスタックトレースの最初の at ... が原因行です。IDE(IntelliJ / VS Code)の警告も丁寧に潰すと、半分のバグは未然に防げます。
Q. Java の習得後に学ぶべき技術は何ですか?
A. 基本構文を抑えたら java-intermediate(コレクション / ジェネリクス / Stream / 例外)に進み、Spring Boot や Web 開発に展開するのが王道です。クラウド時代は Kotlin / Scala への展開も視野に入りますが、まずは Java 標準を固めるのが効率的です。
次のレッスン
次は クラスとオブジェクト クイズ で、Object クラスの toString をオーバーライドして、自作クラスの中身を読みやすい文字列で表現しよう を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- toString の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. toString とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- クラス名は
Solution、メソッド名はpointToStringにすること - メソッドは
public static String pointToString(int x, int y)のシグネチャで、引数 2 つを受け取る - 戻り値は
"Point(x, y)"形式の文字列 (,の後ろに半角スペース 1 つ、括弧は丸括弧())
入出力例
test-cases.txt
pointToString(3, 4) → "Point(3, 4)"
pointToString(0, 0) → "Point(0, 0)"
pointToString(-1, -2) → "Point(-1, -2)"