Java超|Objectクラスと継承のしくみ

どんなクラスも、たどっていけば Object につながる。Object クラスを理解すると、Javaの継承が「バラバラの親子関係」ではなく、ひとつの大きな階層として見えてきます。

Javaでは、クラスを継承によって広げていけます。

親クラスであるスーパークラスを土台にして、子クラスであるサブクラスを作ります。さらに、そのサブクラスをもう一段階拡張して、新しいサブクラスを作ることもできます。

たとえば、ドラゴンボールの世界観で考えると、まず SaiyanWarrior というサイヤ人戦士の共通クラスがあります。

そこから、SuperSaiyanWarrior というスーパーサイヤ人クラスを作れます。さらに、その SuperSaiyanWarrior を土台にして、GodSaiyanWarrior のような、より特別な戦士クラスを作ることもできます。

このように、クラス同士が親子関係でつながっていく構造を、クラスの階層といいます。

ただし、Javaの継承には大切なルールがあります。

できること・できないこと内容
1つのスーパークラスから複数のサブクラスを作るできる
サブクラスをさらに拡張するできる
1つのクラスが複数のスーパークラスを同時に継承するできない
スーパークラスを書かないクラスが Object につながる自動的につながる

特に重要なのが、スーパークラスを明示しないクラスは、自動的に Object クラスを親に持つというルールです。

つまり、Javaで作るすべてのクラスは、最終的には Object クラスにつながっています。

Object クラスは、Javaのあらゆるクラスにとっての共通の親です。

ドラゴンボール風にたとえると、悟空系戦士、ベジータ系戦士、悟飯系戦士、戦闘服、スカウター、任務カードなど、いろいろなクラスを作れます。

それぞれ役割は違います。

でも、Javaの世界では、すべてが最終的に Object という共通の土台につながっています。

この記事では、クラスの階層、単一継承、Object クラス、equals、getClass、toString、toString のオーバーライドについて、サイヤ人戦士の継承階層にたとえて解説していきます。

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

Javaでは、1つのスーパークラスをもとにして、複数のサブクラスを作れます。

ドラゴンボール風にたとえると、まず SaiyanWarrior というサイヤ人戦士の共通クラスがあるとします。

そこから、次のようなサブクラスを作れます。

スーパークラスサブクラスの例意味
SaiyanWarriorSuperSaiyanWarriorスーパーサイヤ人戦士
SaiyanWarriorEliteSaiyanWarriorエリートサイヤ人戦士
SaiyanWarriorSupportSaiyanWarrior支援型サイヤ人戦士

このように、1つの親クラスから複数の子クラスへ分かれていくことができます。

さらに、SuperSaiyanWarrior を土台にして、もう一段階細かいクラスを作ることもできます。

上位クラス下位クラス意味
SaiyanWarriorSuperSaiyanWarriorスーパーサイヤ人の基本クラス
SuperSaiyanWarriorGodSaiyanWarrior神流の覚醒戦士
SuperSaiyanWarriorBlueAuraWarrior青いオーラを持つ覚醒戦士

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

ドラゴンボール風にたとえると、サイヤ人戦士という大きな土台から、スーパーサイヤ人という特別な戦士が生まれ、さらに神流の覚醒戦士や青いオーラの戦士のように細かく分かれていく流れです。

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

サブクラスをさらに拡張した場合、新しく作ったクラスは、直前の親クラスだけでなく、さらに上のクラスの性質も受け継ぎます。

たとえば、次のような関係を考えます。

class SaiyanWarrior
{
}

class SuperSaiyanWarrior extends SaiyanWarrior
{
}

class GodSaiyanWarrior extends SuperSaiyanWarrior
{
}

この場合、GodSaiyanWarrior は SuperSaiyanWarrior を継承しています。

そして、SuperSaiyanWarrior は SaiyanWarrior を継承しています。

つまり、GodSaiyanWarrior は次の要素を持つことになります。

受け継ぐもの内容
GodSaiyanWarrior 自身の特徴神流の覚醒戦士としての独自要素
SuperSaiyanWarrior の特徴スーパーサイヤ人としての要素
SaiyanWarrior の特徴サイヤ人戦士としての共通要素
Object の特徴Javaオブジェクトとしての基本機能

継承は、直前の親だけで止まるわけではありません。

階層を上にたどって、親、その親、さらにその親の性質がつながっていきます。

ドラゴンボール風にたとえると、神流の覚醒戦士は、神流の覚醒戦士としての個性を持っています。

同時に、スーパーサイヤ人でもあり、サイヤ人戦士でもあり、Java上では Object につながるオブジェクトでもあります。

この「上の性質を積み重ねて持つ」という感覚が、クラス階層を理解するうえで大切です。

図:サイヤ人戦士クラスの階層

この図が示していること

この図では、SaiyanWarrior を土台にして、複数のサブクラスが作られる様子を表しています。

SaiyanWarrior の下には、SuperSaiyanWarrior、EliteSaiyanWarrior、SupportSaiyanWarrior があります。

さらに、SuperSaiyanWarrior の下には GodSaiyanWarrior や BlueAuraWarrior が続いています。

ここから分かるのは、Javaでは1つの親クラスから複数の子クラスを作れること、さらにサブクラスをもう一段階拡張できることです。

また、下位クラスは、直前の親クラスだけでなく、その上にあるクラスの性質も積み重ねて受け継ぐことが分かります。

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

Javaでは、1つのクラスが複数のスーパークラスを同時に継承することはできません。

たとえば、次のような2つの親クラスがあるとします。

class SaiyanWarrior
{
}

class MedicalUnit
{
}

この2つを同時に継承して、次のように書くことはできません。

// これはできない
class HealingWarrior extends SaiyanWarrior, MedicalUnit
{
}

Javaのクラス継承では、直接の親クラスは1つだけです。

ドラゴンボール風にたとえると、1人の戦士クラスが「サイヤ人戦士の型」と「医療部隊の型」を、両方とも親クラスとして同時に持つような形は作れません。

なぜなら、複数の親クラスを同時に持つと、同じ名前のメソッドやフィールドがあった場合に、どちらを優先するのかが複雑になりやすいからです。

Javaはここをシンプルに保つため、クラスの継承は1本の親だけにしています。

しくみJavaでの扱い
クラスの継承親クラスは1つだけ
複数の親クラスを同時に継承できない
サブクラスをさらに継承できる

このルールを、単一継承と呼びます。

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

Javaでは、クラスの継承で複数の親クラスを持つことはできません。

しかし、複数の能力や役割を持たせたい場合には、インターフェイスを使えます。

たとえば、ドラゴンボール風に考えると、戦士には次のような能力があります。

能力インターフェイス風の考え方
飛べるFlyable
気を使えるKiUser
仲間を補助できるSupporter
任務を指揮できるLeader

クラスとしての親は1つだけでも、能力の約束は複数組み合わせられます。

class GodSaiyanWarrior extends SaiyanWarrior implements Flyable, KiUser, Leader
{
}

このように考えると、次の違いが見えてきます。

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

今回の中心は Object クラスと継承ですが、Javaが「クラスの親は1つだけ」にしている一方で、「役割や能力はインターフェイスで複数持てる」ようにしていることも、あわせて覚えておくと整理しやすいです。

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

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

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

たとえば、次のように書いたとします。

class SaiyanWarrior
{
}

extends を書いていません。

しかし、考え方としては次のような関係になります。

class SaiyanWarrior extends Object
{
}

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

このルールがあるため、Javaのクラスは最終的にすべて Object クラスにつながります。

書いたクラス実際の継承関係の考え方
class SaiyanWarriorSaiyanWarrior extends Object
class SuperSaiyanWarrior extends SaiyanWarriorSuperSaiyanWarrior extends SaiyanWarrior extends Object
class GodSaiyanWarrior extends SuperSaiyanWarriorGodSaiyanWarrior extends SuperSaiyanWarrior extends SaiyanWarrior extends Object

Object クラスは、Javaのすべてのクラスにとっての一番上の親です。

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

ドラゴンボール風にたとえると、Object クラスは「すべての戦士や道具が、Javaの世界で共通に持っている最も基本の型」です。

サイヤ人戦士、スーパーサイヤ人戦士、スカウター、戦闘服、任務カードなど、いろいろなクラスを作れるとします。

それぞれ見た目も役割も違います。

クラスドラゴンボール風の役割
SaiyanWarriorサイヤ人戦士
SuperSaiyanWarriorスーパーサイヤ人戦士
Scouterスカウター
BattleSuit戦闘服
MissionCard任務カード

しかし、Javaのクラスとしては、どれも最終的に Object につながります。

つまり Object クラスは、ドラゴンボール風に言えば、戦士や道具などの種類が分かれる前にある、Javaオブジェクトとしての共通土台のような存在です。

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

Object クラスを一番上に置いて、クラスの階層を整理すると分かりやすくなります。

階層
最上位の共通の親Object
その子クラスSaiyanWarrior
さらにその子クラスSuperSaiyanWarrior
さらにその子クラスGodSaiyanWarrior

このような階層になっている場合、GodSaiyanWarrior は GodSaiyanWarrior 自身の特徴だけを持っているわけではありません。

次のように、上位クラスの性質も積み重ねています。

クラス受け継ぐもの
ObjectJavaオブジェクトとしての基本機能
SaiyanWarriorサイヤ人戦士としての共通機能
SuperSaiyanWarriorスーパーサイヤ人としての共通機能
GodSaiyanWarrior神流の覚醒戦士としての独自機能

このように見ると、Javaの継承はバラバラの親子関係ではなく、Object を頂点にした大きな階層としてつながっていることが分かります。

図:Object クラスを頂点にした継承階層

この図が示していること

この図では、Object クラスが一番上にあり、その下に SaiyanWarrior や Scouter のようなクラスがつながっています。

SaiyanWarrior の下には SuperSaiyanWarrior、さらにその下に GodSaiyanWarrior が続いています。

この形から、下のクラスほど上のクラスの性質を積み重ねて受け継いでいることが分かります。

また、右下の注意カードでは、1つのクラスが複数のスーパークラスを同時に継承できないことも示しています。

つまりこの図は、次の2つを表しています。

ポイント内容
Object はすべてのクラスの共通の親何も extends しないクラスも Object につながる
Javaのクラス継承は単一継承1つのクラスが持てる直接の親クラスは1つだけ

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

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

これはとても大事です。

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

代表的なメソッドには、次のようなものがあります。

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

これらは、特定のクラスだけのものではありません。

Javaのあらゆるクラスが Object につながっているため、共通して使える基本メソッドです。

ドラゴンボール風に言うと、戦士、スカウター、任務カードなど、種類は違っても、Java世界のオブジェクトとして共通の基本動作を持っているということです。

equals の役割

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

ドラゴンボール風にたとえると、「この戦士と、比較している戦士は同じ存在なのか」を確認するようなイメージです。

warrior1.equals(warrior2);

このように使うと、warrior1 と warrior2 を比較できます。

ただし、equals の動きはクラスによってオーバーライドされることがあります。

何もオーバーライドしていない場合は、基本的には同じオブジェクトを指しているかどうかを見る考え方になります。

一方で、クラス側で equals を作り直せば、名前やIDなどをもとに「同じ戦士かどうか」を判断するようにもできます。

使い方考え方
Object 由来の equals同じオブジェクトかどうかを見る
オーバーライドした equalsクラスに合った同一性の判断をする

ドラゴンボール風にたとえると、ただ同じ場所にいるかではなく、戦士番号や名前などをもとに同一人物か判断するように作り直せる、ということです。

getClass の役割

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

ドラゴンボール風にたとえると、「このオブジェクトは SaiyanWarrior なのか、SuperSaiyanWarrior なのか、GodSaiyanWarrior なのか」を確認するようなものです。

warrior1.getClass();

このメソッドを使うと、変数の型ではなく、実際のオブジェクトのクラス情報を確認できます。

これは、ポリモーフィズムを学んだあとだと理解しやすいです。

たとえば、次のようなコードを考えます。

SaiyanWarrior warrior1 = new SuperSaiyanWarrior();

この場合、変数の型は SaiyanWarrior です。

しかし、実体は SuperSaiyanWarrior です。

getClass() を使うと、実際のオブジェクトのクラス情報を確認できます。

見るもの内容
変数の型SaiyanWarrior
実際のオブジェクトSuperSaiyanWarrior
getClass() で分かるもの実体のクラス情報

ドラゴンボール風にたとえると、名簿上はサイヤ人戦士として扱っていても、実際にはスーパーサイヤ人戦士であることを確認できるような感覚です。

toString の役割

Object クラスのメソッドの中でも、特に大切なのが toString です。

toString は、オブジェクトを文字列として表したものを返すメソッドです。

たとえば、オブジェクトを System.out.println で表示するとき、内部では toString の結果が使われます。

System.out.println(warrior1);

このように書いた場合、warrior1 の toString() が呼ばれ、その戻り値が表示されます。

つまり、toString は「そのオブジェクトを文字列でどう表すか」を決めるメソッドです。

ドラゴンボール風にたとえると、戦士が自分の情報を文字で名乗るための基本メソッドです。

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

何も準備しないままオブジェクトを表示すると、Object クラスから受け継いだ toString がそのまま使われます。

その場合、次のような形式の文字列が表示されることがあります。

SaiyanWarrior@1a2b3c

これは Java としては正しい表示です。

ただし、人間にとってはあまり分かりやすくありません。

表示分かること
SaiyanWarrior@1a2b3cクラス名と識別用の情報らしきもの
名前: 悟空、流派: 亀仙流オブジェクトの意味ある情報

ドラゴンボール風にたとえると、戦士を紹介してほしいのに、名前や流派ではなく、管理番号のようなものだけが表示される感覚です。

確認や学習には少し不便です。

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

そこで便利なのが、toString のオーバーライドです。

自分のクラスで toString を作り直すと、そのオブジェクトを表示したときに、意味のある文字列を返せるようになります。

たとえば、SaiyanWarrior クラスなら、次のようにできます。

class SaiyanWarrior
{
    private String name;
    private String styleName;

    public SaiyanWarrior(String name, String styleName)
    {
        this.name = name;
        this.styleName = styleName;
    }

    public String toString()
    {
        return "名前: " + name + "、流派: " + styleName;
    }
}

このように toString をオーバーライドしておくと、次のように表示できます。

SaiyanWarrior warrior1 = new SaiyanWarrior("悟空", "亀仙流");
System.out.println(warrior1);

表示イメージは次のようになります。

名前: 悟空、流派: 亀仙流

これは、Object から受け継いだ toString を、SaiyanWarrior クラスに合うように作り直している状態です。

ドラゴンボール風にたとえると、ただの管理番号ではなく、戦士が自分の名前や流派をきちんと名乗れるようにするイメージです。

toString はなぜ便利なのか

toString をオーバーライドすると、デバッグや表示がとても分かりやすくなります。

たとえば、オブジェクトの中身を確認したいときに、意味のある情報が表示されるからです。

toString をそのまま使う場合toString をオーバーライドした場合
SaiyanWarrior@1a2b3c のような表示名前や流派など意味のある表示
中身が分かりにくい状態を確認しやすい
学習中に混乱しやすいオブジェクトの内容をつかみやすい

toString は、Javaのクラスでとてもよく使うオーバーライド対象のひとつです。

図:Object の代表メソッドと toString のオーバーライド

この図が示していること

この図では、Object クラスが持つ代表的なメソッドと、toString をオーバーライドする意味を整理しています。

Object クラスには、equals、getClass、toString のような基本メソッドがあります。

すべてのクラスは Object につながっているため、こうしたメソッドを共通に持ちます。

右側では、SaiyanWarrior クラスが toString を自分用に作り直しています。

その結果、何も準備していない場合は SaiyanWarrior@1a2b3c のような表示になりやすいところを、名前や流派が分かる表示にできます。

この図から分かることは、Object のメソッドはただ受け継ぐだけでなく、必要に応じて自分のクラスらしくオーバーライドできるということです。

サンプルプログラムで Object と toString を確認する

ここでは、Object から受け継いだ toString を、SaiyanWarrior クラスでオーバーライドする例を見てみます。

class SaiyanWarrior
{
    private String name;
    private String styleName;

    public SaiyanWarrior(String name, String styleName)
    {
        this.name = name;
        this.styleName = styleName;
    }

    public String toString()
    {
        return "名前: " + name + "、流派: " + styleName;
    }
}

class Main
{
    public static void main(String[] args)
    {
        SaiyanWarrior warrior1 = new SaiyanWarrior("悟空", "亀仙流");

        System.out.println(warrior1);
        System.out.println(warrior1.getClass());
    }
}

このプログラムでは、SaiyanWarrior クラスに toString() を定義しています。

public String toString()
{
    return "名前: " + name + "、流派: " + styleName;
}

これは、Object から受け継いだ toString() を、SaiyanWarrior 用に作り直しているということです。

main メソッドでは、次のようにオブジェクトをそのまま表示しています。

System.out.println(warrior1);

このとき、warrior1 の toString() が呼ばれます。

そのため、管理番号のような表示ではなく、名前と流派が分かる表示になります。

実行結果のイメージは次のようになります。

名前: 悟空、流派: 亀仙流
class SaiyanWarrior

1行目では、toString() の戻り値が表示されています。

2行目では、getClass() によって実際のクラス情報が表示されています。

このように、Object 由来のメソッドは、Javaのどのクラスでも関係してきます。

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

Object クラスは、普段のコードで明示的に extends Object と書くことが少ないため、最初は少し地味に感じるかもしれません。

しかし、Javaの継承を理解するうえではとても重要です。

理由は、次のとおりです。

理由内容
すべてのクラスが Object につながるJavaの継承階層の根になる
基本メソッドを共通で持つequals、getClass、toString などが使える
オーバーライドの理解につながるtoString などを自分用に作り直せる
ポリモーフィズムの土台になるObject 型でさまざまなオブジェクトを扱える考え方につながる

Object クラスを知ると、Javaのクラスが完全にバラバラに存在しているのではなく、1つの大きな階層としてつながっていることが分かります。

Object 型でいろいろなオブジェクトを扱える

Object はすべてのクラスの親なので、Object 型の変数には、いろいろなクラスのオブジェクトを入れられます。

Object obj1 = new SaiyanWarrior("悟空", "亀仙流");
Object obj2 = new Scouter();
Object obj3 = new MissionCard();

これは、SaiyanWarrior も Scouter も MissionCard も、すべて Object の一種だからです。

ドラゴンボール風にたとえると、戦士、スカウター、任務カードのように種類が違っても、Javaの世界ではすべて「オブジェクト」として扱えるということです。

ただし、Object 型として扱う場合、呼び出せるのは基本的に Object にあるメソッドです。

そのクラス独自のメソッドをそのまま呼びたい場合は、型を意識する必要があります。

この考え方は、スーパークラス型でサブクラスを扱うポリモーフィズムともつながります。

ドラゴンボール風に Object クラスを整理する

Javaのクラスは、見た目にはそれぞれ別の存在です。

ドラゴンボール風にたとえると、次のようにさまざまなクラスがあります。

クラス役割
SaiyanWarriorサイヤ人戦士
SuperSaiyanWarriorスーパーサイヤ人戦士
GodSaiyanWarrior神流の覚醒戦士
Scouterスカウター
MissionCard任務カード

しかし、どれも最終的には Object クラスにつながっています。

そのため、どのクラスも Object 由来の基本メソッドを持っています。

Object のメソッドドラゴンボール風のイメージ
equals同じ戦士や道具か確認する
getClass何のクラスの実体か調べる
toString自分の情報を文字で名乗る

Object クラスは、すべてのクラスが最初に受け継いでいる共通の親です。

この考え方が分かると、Javaの継承は、単なる親子関係の集まりではなく、Object を頂点にしたひとつの大きな仕組みとして見えてきます。

この内容で押さえておきたいポイント

ポイント内容
クラスの階層親から子へ、さらにその子へとつながる構造
サブクラスの拡張上位クラスのメンバも積み重ねて受け継ぐ
Javaの単一継承1つのクラスが直接継承できる親クラスは1つだけ
Object クラスすべてのクラスの共通の親
extends を書かないクラス自動的に Object を親に持つ
equalsオブジェクト同士を比較する基本メソッド
getClass実際のクラス情報を確認するメソッド
toStringオブジェクトを文字列で表すメソッド
toString のオーバーライドオブジェクトを分かりやすく表示できる

Object クラスを理解すると、Javaのクラスがすべて共通の土台につながっていることが分かります。

そして、equals、getClass、toString のような基本メソッドが、どのクラスでも使える理由も見えてきます。

特に toString は、自分のクラスらしくオーバーライドすることで、オブジェクトの状態を分かりやすく表示できる便利なメソッドです。