Java入門|Objectクラスと継承のしくみ(すべてのクラスの共通の親)

どんな戦士も、たどっていけば共通の原点に行き着く。
Objectクラスを知ると、Javaの継承がバラバラの仕組みではなく、ひとつの大きな階層としてつながっていることが見えてくる。

ここまで、Javaではクラスを継承によって広げていけることを見てきました。
スーパークラスを土台にしてサブクラスを作り、そのサブクラスをさらに拡張していくことで、クラスどうしは親子関係を持ちながら整理されていきます。こうしてできるのが、クラスの階層です。

ドラゴンボールでたとえると、これは戦士たちの系譜をたどっていくようなものです。
ある戦士クラスを土台にして、その特徴を受け継いだ別の戦士クラスが生まれ、さらにその先に新しい戦士クラスが生まれていきます。すると、それぞれは別の存在でありながら、共通のルーツを持つ関係として整理できます。

ただ、ここで大事なのは、Javaの継承にはルールがあることです。

  • 1つのスーパークラスから複数のサブクラスを作ることはできる
  • サブクラスをさらに拡張して、その先のサブクラスを作ることもできる
  • でも、1つのサブクラスが複数のスーパークラスを同時に継承することはできない

そして、もうひとつとても重要なルールがあります。
それが、スーパークラスを明示しないクラスは、自動的に Object クラスを親に持つ というしくみです。

つまり、Javaで作るクラスは、最終的にはすべて Object クラスにつながっています。
Object クラスは、Javaのあらゆるクラスにとっての共通の親です。

今回は、この Object クラスと継承のしくみを、ドラゴンボールの戦士たちの系譜に置きかえながら、やわらかく丁寧に整理していきます。特に、

  • クラスの階層はどう作られるのか
  • Javaでできる継承とできない継承は何か
  • Object クラスはなぜ「すべてのクラスの共通の親」といえるのか
  • Object クラスの主なメソッドはどんな役割を持つのか
  • とくに toString がなぜ便利なのか

を順番に見ていきます。

クラスの階層を作るとはどういうことか

Javaでは、1つのスーパークラスをもとにして複数のサブクラスを作れます。
さらに、そのサブクラスをもう一段階拡張して、新しいサブクラスを作ることもできます。

ドラゴンボールで考えると、たとえば共通の土台となる Warrior クラスがあり、そこから

  • Saiyan(サイヤ人)
  • Namekian(ナメック星人)

のようなサブクラスが分かれるイメージです。

さらに Saiyan から

  • EliteSaiyan(エリートサイヤ人)
  • EarthRaisedSaiyan(地球育ちのサイヤ人)

のようなクラスが広がることもあります。

このように、親から子へ、さらにその子へとつながっていく構造を、クラスの階層と考えるとわかりやすいです。

サブクラスをさらに拡張すると何が起こるのか

サブクラスをさらに拡張した場合、その新しいクラスは直前の親クラスのメンバだけでなく、そのさらに上にある元のスーパークラスのメンバも受け継ぎます。

これは継承が途中で切れるのではなく、階層をたどって連続して受け継がれるからです。

ドラゴンボール風に言うと、EliteSaiyan が Saiyan を継承していて、その Saiyan がさらに上の Warrior を継承しているなら、EliteSaiyan は

  • EliteSaiyan 自身の特徴
  • Saiyan としての特徴
  • Warrior としての共通機能

をあわせ持つことになります。

つまり、下のクラスに行くほど、上の階層の要素を積み重ねながら個性が増していくわけです。

この感覚があると、継承は単なる 1 対 1 の親子関係ではなく、縦につながる系譜として理解しやすくなります。

Javaでは複数のスーパークラスを同時に継承できない

ここで、とても大切なルールがあります。
Java では、1つのクラスが複数のスーパークラスを同時に継承することはできません。

つまり、たとえば

  • Saiyan
  • Namekian

という2つのクラスがあったとして、その両方を同時に継承する1つのクラスは作れません。

これは、複数の親から同時に性質を受け取ると、どちらのルールを優先すべきかが複雑になりやすいからです。

ドラゴンボールでたとえるなら、1人の戦士クラスが「サイヤ人の型」と「ナメック星人の型」を同時に親として持つような、無理な系譜は作れないということです。

Javaはここをシンプルに保つために、クラスの継承は1本の親だけ という考え方を取っています。

それでも共通の能力を増やしたいとき

クラスの継承では複数の親を持てませんが、Javaにはそれを補う考え方としてインターフェイスがあります。
これは、複数のクラス的な能力の約束を組み合わせるためのしくみです。

今回の中心は Object クラスと継承なので、ここでは細かく踏み込みませんが、大事なのは次の整理です。

しくみできること
クラスの継承親クラスは1つだけ
インターフェイス能力の約束を複数組み合わせられる

ドラゴンボール風に言えば、種族としての親は1つに決まっていても、

  • 飛べる
  • 気を使える
  • 感知できる

のような能力の約束は重ねて持てる、という整理です。

Objectクラスはすべてのクラスの共通の親

ここからが今回の中心です。

Javaでは、クラスを宣言するときにスーパークラスを指定しない場合、そのクラスは自動的に Object クラスをスーパークラスとして持つことになります。

たとえば、

class Saiyan
{
    ...
}

のように書いたとしても、実際の考え方としては

class Saiyan extends Object
{
    ...
}

と同じ関係になっています。

つまり、何も書いていなくても、そのクラスは Object の子クラスなのです。

このルールがあるため、Java のクラスはすべて最終的に Object クラスにつながっています。
だから Object クラスは、すべてのクラスの共通の親 と呼ばれるわけです。

ドラゴンボールで考えるObjectクラス

ドラゴンボールでたとえるなら、Object クラスは「すべての戦士の最も大もとの共通土台」です。

たとえば、

  • サイヤ人
  • ナメック星人
  • 地球人戦士
  • 人造人間風の戦士クラス

のように見た目も特徴も違うクラスがあったとしても、もっと上までさかのぼれば、みんな「Java のオブジェクトとして共通に持つ基本機能」を受け継いでいます。

それが Object クラスの役割です。

つまり Object クラスは、ドラゴンボールの世界でいえば、
種族や流派が分かれる前の、全戦士共通の最も基本的な存在の型
のようなものです。

Objectクラスにつながる階層の見方

この関係を表で整理すると、こんなイメージになります。

階層
最上位の共通の親Object
その子クラスSaiyan
さらにその子クラスEliteSaiyan

このように、EliteSaiyan は Saiyan を継承し、Saiyan は Object を継承しているので、EliteSaiyan も最終的には Object のメンバを受け継いでいることになります。

つまり、どんなクラスも Object クラスから完全に切り離されているわけではありません。

だからJavaのすべてのクラスはObjectのメンバを持つ

Object クラスが共通の親であるということは、Java のすべてのクラスが Object クラスのメンバを継承しているということです。

これはかなり大事な意味を持ちます。

なぜなら、Object クラスが持っている基本メソッドを、どんなクラスのオブジェクトでも使えることになるからです。

代表的なものとして、次の3つがあります。

メソッド役割
equals(Object obj)引数と同じオブジェクトかどうかを調べる
getClass()オブジェクトのクラス情報を返す
toString()オブジェクトを表す文字列を返す

この3つは、Java のあらゆるクラスにとって基本的なメソッドです。
どんなクラスでも Object につながっているからこそ、共通に使えるわけです。

equals の役割

equals は、あるオブジェクトが引数として渡されたオブジェクトと同じものかどうかを調べるためのメソッドです。

ドラゴンボールでたとえるなら、
「この戦士と、今見ている戦士は同じ存在か」
を確かめるようなイメージです。

戦士名が似ているとか、同じ流派だとかではなく、オブジェクトとして同じかどうかを見るための基本メソッドだと考えると整理しやすいです。

getClass の役割

getClass は、そのオブジェクトがどのクラスに属しているかを返すメソッドです。

ドラゴンボールで考えると、
「この戦士はサイヤ人クラスなのか、エリートサイヤ人クラスなのか」
を確認するような感覚です。

見た目の変数型ではなく、そのオブジェクト自身のクラス情報に触れられるという点で、継承の理解ともつながります。

toString の役割

今回の中で特に大切なのが toString です。
toString は、オブジェクトを文字列で表したものを返すメソッドです。

そして、オブジェクトをそのまま出力しようとしたときには、この toString が呼ばれます。
たとえば、あるオブジェクトを System.out.println で表示するとき、内部では toString の結果が使われます。

これはとても便利なしくみです。

なぜなら、クラス側で toString をうまく定義しておけば、そのオブジェクトを表示したときに、意味のわかる文字列を出せるからです。

Objectから受け継いだtoStringをそのまま使うとどうなるか

何も準備しないままオブジェクトを表示すると、Object クラスから継承した toString がそのまま使われます。
その結果、Saiyan@数値 のような形式の文字列が表示されます。※数値は16進数

これは Java としては正しいのですが、人にとってはあまりわかりやすくありません。

ドラゴンボール風に言えば、戦士を表示したのに

  • 戦士名
  • 戦闘力
  • 所属

のような意味ある情報ではなく、機械的な識別表示だけが出てくる感覚です。

それでは説明や確認には少し不便です。

toStringをオーバーライドすると便利になる

だからこそ、toString は自分のクラスでオーバーライドすると便利です。

たとえば Saiyan クラスなら、

  • 名前
  • 戦闘力
  • 担当エリア

などをわかりやすい文字列にして返すように toString を作り直せます。

すると、そのオブジェクトを出力しただけで、
「ベジータ・戦闘力 18000・担当エリア 7」
のような、意味のある表示にしやすくなります。

ドラゴンボールでたとえるなら、ただの識別コードではなく、
この戦士は誰で、どんな状態なのかを自分から名乗るようにする
イメージです。

これが toString をオーバーライドする大きな利点です。

なぜObjectクラスを知ることが大切なのか

Object クラスは、直接コードに書く場面が少ないため、最初は少し地味に感じるかもしれません。
でも実際には、Java の継承全体を理解するための土台になっています。

なぜなら、

  • すべてのクラスは最終的に Object につながる
  • だからどのオブジェクトにも共通の基本メソッドがある
  • そのうえで各クラスが自分らしくオーバーライドしていく

という流れが、Java のクラス設計全体を支えているからです。

つまり Object クラスを知ることは、
継承のいちばん根っこのルールを知ること
でもあります。

ドラゴンボールで感覚的に整理する

最後に、ドラゴンボールの感覚で整理してみましょう。

Java のクラスたちは、それぞれ別々の戦士のように見えます。
でも、もっと上までたどっていくと、全員が共通に受け継いでいる「戦士としての最も基本の土台」があります。
それが Object クラスです。

  • サイヤ人も Object につながる
  • エリートサイヤ人も Object につながる
  • 別の系統の戦士クラスも Object につながる

だから、どの戦士クラスも

  • equals
  • getClass
  • toString

のような基本機能を持っています。

そして toString のようなメソッドは、そのまま使うだけでなく、自分のクラスらしく作り直すことでさらに便利になります。

つまり Object クラスは、
すべての戦士が最初に受け継いでいる共通の親
であり、Java の継承を一本の大きな流れとしてつないでいる存在なのです。

図でObjectクラスと継承の関係を整理する

上部の Objectクラス が、Java のすべてのクラスの出発点です。
そこから Saiyanクラス や Namekianクラス が分かれ、さらに Saiyanクラス の下に EliteSaiyanクラス がつながっています。

この形から、下のクラスほど上のクラスのメンバを積み重ねて受け継いでいることが見えてきます。

右下のバツ印つきの図は、1つのクラスが同時に複数のスーパークラスを持つことはできない、という Java のルールを表しています。

そして equals()、getClass()、toString() は、Object クラスが持っている代表的なメソッドです。
どのクラスも最終的に Object につながるので、こうした基本メソッドを共通に持てるわけです。