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

継承は便利ですが、親クラスを何本も同時に持てるわけではありません。Javaでは親クラスは1本だけ。その最上位には、すべてのクラスを支える Object クラスがあります。

Javaの継承では、親クラスから子クラスへ機能を受け継ぎながら、クラスを発展させていけます。

ドラゴンボールの世界観でたとえると、まず SaiyanWarrior というサイヤ人戦士の共通の型があります。そこから、SuperSaiyanWarrior、EliteSaiyanWarrior、SupportSaiyanWarrior のように、役割ごとのクラスを作っていくイメージです。

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

それは、1つのクラスが同時に複数のスーパークラスを持つことはできないということです。

つまり、1つの子クラスが2つ以上の親クラスを同時に継承することはできません。

そして、もう1つ重要なルールがあります。

それが、スーパークラスを明示しないクラスは、自動的に Object クラスを親に持つという仕組みです。

このため、Javaのすべてのクラスは、最終的に Object クラスにつながっています。

Object クラスは、Javaにおけるすべてのクラスの共通の親です。

ドラゴンボール風にたとえると、サイヤ人戦士、スーパーサイヤ人戦士、スカウター、戦闘服、任務カードなど、さまざまなクラスがあっても、Javaの世界ではすべて Javaオブジェクトとしての共通土台を持っているということです。

この記事では、多重継承の制限、単一継承、インターフェイス、Object クラス、equals、getClass、toString について、ドラゴンボールの世界観で解説していきます。

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

まず押さえておきたいのは、Javaでは1つのクラスが複数のスーパークラスを同時に継承できないという点です。

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

class SaiyanWarrior
{
}

class MedicalUnit
{
}

SaiyanWarrior はサイヤ人戦士を表すクラスです。
MedicalUnit は回復部隊を表すクラスです。

この2つを同時に親クラスとして持つクラスを作ることはできません。

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

Javaでは、クラスの継承で指定できる親クラスは1つだけです。

書き方Javaで可能か
class SuperSaiyanWarrior extends SaiyanWarrior可能
class HealingWarrior extends SaiyanWarrior, MedicalUnit不可能

ドラゴンボール風にたとえると、1人の戦士クラスが「サイヤ人戦士の型」と「回復部隊の型」を、同時に親として持つことはできないということです。

スーパーサイヤ人戦士は、サイヤ人戦士を土台にして作ることはできます。
しかし、サイヤ人戦士クラスと回復部隊クラスの両方を、親クラスとして同時に継承することはできません。

なぜ多重継承を制限しているのか

Javaがクラスの多重継承を許していない理由は、継承関係を分かりやすく保つためです。

もし1つのクラスが複数の親クラスを持てると、次のような問題が起こりやすくなります。

問題内容
同じ名前のメソッドがあるどちらの親クラスのメソッドを使うのか迷う
同じ名前のフィールドがあるどちらの値を参照するのか分かりにくい
初期化の順番が複雑になるどの親クラスから先に作るのか分かりにくい
設計の見通しが悪くなるクラスの責任範囲が曖昧になりやすい

ドラゴンボール風にたとえると、1人の戦士が「亀仙流の師匠」と「医療部隊の師匠」を同時に親として持ち、両方から同じ名前の技を受け継いだら、どちらの技を優先するのか分かりにくくなります。

たとえば、両方の親クラスに train() があった場合、HealingWarrior が train() を呼び出したときに、どちらの train() を使うのかが問題になります。

Javaはこのような混乱を避けるため、クラスの親は1つだけにしています。

ただし、親から子へ、さらにその子へと縦につなげることはできます。

class SaiyanWarrior
{
}

class SuperSaiyanWarrior extends SaiyanWarrior
{
}

class GodSaiyanWarrior extends SuperSaiyanWarrior
{
}

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

つまり、Javaでは複数の親を横に並べるのではなく、1本の系譜として縦につなげる形になります。

しくみJavaでの扱い
クラスの直接の親1つだけ
複数の親クラスを同時に継承できない
親から子、さらにその子へ継承できる

図:Javaでは親クラスは1本だけ

この図が示していること

この図では、Javaのクラス継承では親クラスを1つだけ指定できることを表しています。

左側では、SaiyanWarrior から SuperSaiyanWarrior、さらに GodSaiyanWarrior へと縦につながる継承を示しています。

このように、親から子へ、さらにその子へと継承することはできます。

一方、右側では HealingWarrior が SaiyanWarrior と MedicalUnit の2つを同時に親にしようとしており、バツ印が付いています。

ここから分かるのは、Javaでは複数のスーパークラスを同時に継承できないということです。

Javaの継承は、複雑な網のように親を増やすのではなく、親クラスを1つに絞って分かりやすい系譜を作る仕組みです。

それでも複数の能力を持たせたいとき

クラスの多重継承はできません。

しかし、複数の能力や役割を持たせたい場面はあります。

そのときに使うのがインターフェイスです。

インターフェイスは、クラスの親そのものではなく、「この能力を持つ」という約束を表す仕組みです。

ドラゴンボール風にたとえると、戦士としての親クラスは1つでも、次のような能力の約束を複数持てるイメージです。

能力インターフェイス風の考え方
飛べるFlyable
気を使えるKiUser
回復できるHealer
索敵できるSensor
指揮できるLeader

たとえば、次のような形です。

class HealingWarrior extends SaiyanWarrior implements KiUser, Healer
{
}

この場合、HealingWarrior は SaiyanWarrior を親クラスとして継承しています。

さらに、KiUser と Healer という能力の約束を持っています。

整理すると、次のようになります。

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

つまり Java は、クラスの多重継承を避けつつ、必要な柔軟性はインターフェイスで補う仕組みを持っています。

ドラゴンボール風に言うと、戦士としての血筋や土台は1本にしながら、飛行能力、気の操作、回復能力、索敵能力などの「できること」は複数持たせられるということです。

スーパークラスを指定しないクラスはどうなるのか

ここからが、Object クラスの話です。

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

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

class SaiyanWarrior
{
}

このコードでは、extends を書いていません。

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

class SaiyanWarrior extends Object
{
}

つまり、明示的に親クラスを書いていなくても、SaiyanWarrior は Object クラスのサブクラスです。

書いた形継承関係の考え方
class SaiyanWarriorSaiyanWarrior extends Object
class SuperSaiyanWarrior extends SaiyanWarriorSuperSaiyanWarrior extends SaiyanWarrior extends Object
class GodSaiyanWarrior extends SuperSaiyanWarriorGodSaiyanWarrior extends SuperSaiyanWarrior extends SaiyanWarrior extends Object

このルールによって、Javaのすべてのクラスは最終的に Object クラスへつながります。

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

Object クラスがすべてのクラスの共通の親と呼ばれる理由は、スーパークラスを明示しないクラスが自動的に Object を継承するからです。

ドラゴンボール風にたとえると、Object クラスは、戦士や道具などの種類が分かれる前にある、Javaオブジェクトとしての最も基本的な土台です。

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

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

これらは見た目も役割も違います。

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

つまり、Object クラスは、すべてのクラスが共通して受け継ぐ、Java世界の最上位の親です。

継承の階層はどうつながっていくのか

Javaの継承関係は、上から下へと連なっていきます。

たとえば、次のような階層を考えます。

階層クラス例ドラゴンボール風のイメージ
最上位Objectすべての共通土台
その下SaiyanWarriorサイヤ人戦士
さらに下SuperSaiyanWarriorスーパーサイヤ人戦士
さらに下GodSaiyanWarrior神流の覚醒戦士

このとき、GodSaiyanWarrior は GodSaiyanWarrior 自身の特徴だけを持つわけではありません。

上の階層から、次のように受け継いでいます。

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

つまり、下のクラスほど、上位クラスの性質を積み重ねながら、自分の個性を追加していく構造になります。

ドラゴンボール風にたとえると、神流の覚醒戦士は、神流の覚醒戦士であると同時に、スーパーサイヤ人戦士でもあり、サイヤ人戦士でもあり、Java上では Object につながるオブジェクトでもあるということです。

図:多重継承の制限とObjectクラスの関係

この図が示していること

この図では、Javaの継承が Object クラスを頂点にした階層としてつながる様子を表しています。

Object クラスの下に SaiyanWarrior があり、その下に SuperSaiyanWarrior、さらに GodSaiyanWarrior が続いています。

この構造から、Javaのクラスは最終的に Object クラスへつながることが分かります。

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

一方で、右下では KiUser や Healer のようなインターフェイスを使えば、複数の能力の約束を組み合わせられることを示しています。

この図のポイントは次の2つです。

ポイント内容
Object クラスすべてのクラスの共通の親
多重継承の制限クラスの親は1つだけ

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

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

そのため、どんなクラスのオブジェクトでも、Object クラス由来の基本メソッドを持っています。

代表的なメソッドは次の3つです。

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

これらは、特定のクラスだけに用意されている特別なメソッドではありません。

SaiyanWarrior、SuperSaiyanWarrior、Scouter、MissionCard のように、どんなクラスでも最終的に Object につながるため、共通して使える基本機能です。

equals は何をするメソッドなのか

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

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

warrior1.equals(warrior2);

ドラゴンボール風にたとえると、今見ている戦士と、比べようとしている戦士が同じ存在かどうかを確認するようなイメージです。

ただし、equals の意味はクラスによって作り直されることがあります。

Object 由来の equals は、基本的には同じオブジェクトを指しているかどうかを見る考え方です。

一方で、クラス側で equals をオーバーライドすれば、戦士番号や名前をもとに「同じ戦士かどうか」を判断するようにもできます。

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

ドラゴンボール風に言うと、単に同じ実体を見ているかだけでなく、戦士IDや名前などを基準にして、同じ登録戦士かどうかを判断するように作り直せるということです。

getClass は何をするメソッドなのか

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

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

warrior1.getClass();

ドラゴンボール風にたとえると、この戦士は SaiyanWarrior なのか、SuperSaiyanWarrior なのか、GodSaiyanWarrior なのかを確認するような感覚です。

これは、ポリモーフィズムを考えるときにも大切です。

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

SaiyanWarrior warrior1 = new SuperSaiyanWarrior();

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

しかし、実際のオブジェクトは SuperSaiyanWarrior です。

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

getClass を使うと、見た目の変数型ではなく、実際のオブジェクトのクラス情報を確認できます。

ドラゴンボール風にたとえると、名簿上はサイヤ人戦士として扱っていても、スカウターで実体を調べるとスーパーサイヤ人戦士だと分かるようなイメージです。

toString は何をするメソッドなのか

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

そして、オブジェクトをそのまま出力しようとしたとき、この toString が呼び出されます。

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

System.out.println(warrior1);

このとき、内部的には warrior1 の toString() の結果が表示されます。

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

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

何も準備しないと Object クラスの toString が使われる

自分のクラスで toString を用意しなかった場合、Object クラスから継承した toString がそのまま使われます。

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

SaiyanWarrior@1a2b3c

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

しかし、人間にとっては少し分かりにくいです。

表示印象
SaiyanWarrior@1a2b3c機械的な識別表示
名前: 悟空、流派: 亀仙流意味が分かりやすい表示

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

このままでは、確認やデバッグには少し不便です。

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 の代表メソッドと共通機能

この図が示していること

この図では、Object クラスが持つ代表的なメソッドを整理しています。

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

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

また、toString は自分のクラスでオーバーライドすると、分かりやすい文字列を返せるようになります。

状態表示例
Object 由来の toString をそのまま使うSaiyanWarrior@1a2b3c
toString をオーバーライドする名前: 悟空、流派: 亀仙流

この図から分かることは、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 SuperSaiyanWarrior extends SaiyanWarrior
{
    public SuperSaiyanWarrior(String name, String styleName)
    {
        super(name, styleName);
    }
}

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

        System.out.println(warrior1);
        System.out.println(warrior2);

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

        System.out.println(warrior1.equals(warrior2));
    }
}

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

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

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

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

System.out.println(warrior1);
System.out.println(warrior2);

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

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

名前: 天津飯、流派: 鶴仙流
名前: 悟空、流派: 亀仙流
class SaiyanWarrior
class SuperSaiyanWarrior
false

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

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

warrior2 の変数の型は SaiyanWarrior ですが、実体は SuperSaiyanWarrior です。

そのため、getClass() では SuperSaiyanWarrior の情報が表示されます。

最後の false は、warrior1 と warrior2 が同じオブジェクトではないことを表しています。

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

Object クラスを知ると継承の全体像が見える

Object クラスは、Javaの継承全体を支える土台です。

Object クラスを理解すると、次の流れが見えてきます。

流れ内容
Javaのクラス継承は親クラス1つだけ多重継承はできない
親をたどると最終的に Object に行き着くすべてのクラスの共通の親
すべてのクラスは Object の基本メソッドを持つequals、getClass、toString など
必要に応じて基本メソッドを作り直せるtoString などをオーバーライドできる

この見方ができると、Javaの継承はバラバラの親子関係ではなく、Object を最上位にした一本の大きな系譜として理解できます。

ドラゴンボール風にたとえると、戦士ごとに個性は違います。

サイヤ人戦士、スーパーサイヤ人戦士、神流の覚醒戦士、スカウター、戦闘服、任務カードなど、クラスの種類も違います。

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

ドラゴンボール風に多重継承と Object クラスを整理する

Javaのクラスは、いろいろな戦士や道具の型のようなものです。

たとえば、次のようなクラスがあります。

クラスドラゴンボール風のイメージ
SaiyanWarriorサイヤ人戦士
SuperSaiyanWarriorスーパーサイヤ人戦士
GodSaiyanWarrior神流の覚醒戦士
Scouterスカウター
MissionCard任務カード

ただし、1つのクラスが複数の親クラスを同時に持つことはできません。

親クラスは1本だけです。

その一方で、どんなクラスも上までたどれば Object クラスに行き着きます。

だから、どのクラスも次の基本機能を共通に持っています。

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

つまり、Javaの継承は、親クラスは1本だけでつながり、その最上位には Object があるという構造です。

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

ポイント内容
多重継承Javaでは1つのクラスが複数のスーパークラスを同時に継承できない
単一継承クラスの直接の親は1つだけ
継承階層親から子へ、さらにその子へと縦につながる
インターフェイス複数の能力の約束を組み合わせられる
Object クラスすべてのクラスの共通の親
extends を書かないクラス自動的に Object を親に持つ
equalsオブジェクト同士を比較する
getClass実際のクラス情報を返す
toStringオブジェクトを文字列で表す

多重継承の制限を知ると、Javaがクラスの親子関係をシンプルに保っていることが分かります。

そして Object クラスを知ると、Javaのすべてのクラスが共通の土台につながっていることが見えてきます。

この2つを合わせて理解すると、Javaの継承は「自由に枝分かれする複雑な網」ではなく、Object を頂点とした一本の系譜として整理できます。