Java道|多重継承の制限とObjectクラス

継承は自由に広がるようでいて、親クラスは1本だけ。
Object クラスを知ると、Javaのクラス構造が一本の大きな系譜として見えてきます。

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

鬼滅の刃風にたとえると、鬼殺隊士という共通の型があり、そこから柱、継子、隠のように役割ごとのクラスが生まれていくイメージです。

ただし、Javaの継承には大切なルールがあります。
それは、1つのクラスが同時に複数のスーパークラスを持つことはできない ということです。

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

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

このため、Javaのすべてのクラスは、最終的に Object クラスにつながっています。
Object クラスは、Javaにおけるすべてのクラスの共通の親です。

鬼滅の刃風にたとえると、隊士、柱、日輪刀、任務書など、さまざまなクラスがあっても、Javaの世界ではすべて「オブジェクト」としての共通土台を持っているということです。

↓クリックすると拡大表示されます。

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

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

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

class DemonSlayer
{
}

class MedicalUnit
{
}

DemonSlayer は鬼殺隊士を表すクラスです。
MedicalUnit は医療部隊を表すクラスです。

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

// これはできない
class HealingPillar extends DemonSlayer, MedicalUnit
{
}

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

書き方Javaで可能か
class PillarSlayer extends DemonSlayer可能
class HealingPillar extends DemonSlayer, MedicalUnit不可能

鬼滅の刃風にたとえると、1人の隊士クラスが「鬼殺隊士の型」と「医療部隊の型」を、同時に親として持つことはできないということです。

↓クリックすると拡大表示されます。

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

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

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

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

鬼滅の刃風にたとえると、1人の隊士が「水の呼吸の師匠」と「医療部隊の師匠」を同時に親として持ち、両方から同じ名前の奥義を受け継いだら、どちらの奥義を優先するのか分かりにくくなります。

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

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

class DemonSlayer
{
}

class PillarSlayer extends DemonSlayer
{
}

class WaterPillarSlayer extends PillarSlayer
{
}

この場合、WaterPillarSlayer は PillarSlayer を継承し、PillarSlayer は DemonSlayer を継承しています。

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

↓クリックすると拡大表示されます。

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

クラスの多重継承はできません。
しかし、複数の能力や役割を持たせたい場面はあります。

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

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

鬼滅の刃風にたとえると、隊士としての親クラスは1つでも、次のような能力の約束を複数持てるイメージです。

能力インターフェイス風の考え方
呼吸を使えるBreathingUser
指揮できるLeader
治療できるHealer
索敵できるSensor

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

class HealingPillar extends DemonSlayer implements BreathingUser, Healer
{
}

この場合、HealingPillar は DemonSlayer を親クラスとして継承しています。
さらに、BreathingUser と Healer という能力の約束を持っています。

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

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

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

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

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

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

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

class DemonSlayer
{
}

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

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

class DemonSlayer extends Object
{
}

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

書いた形継承関係の考え方
class DemonSlayerDemonSlayer extends Object
class PillarSlayer extends DemonSlayerPillarSlayer extends DemonSlayer extends Object
class WaterPillarSlayer extends PillarSlayerWaterPillarSlayer extends PillarSlayer extends DemonSlayer extends Object

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

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

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

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

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

クラス鬼滅の刃風の役割
DemonSlayer鬼殺隊士
PillarSlayer
NichirinSword日輪刀
MissionScroll任務書
CrowMessenger鎹鴉

これらは見た目も役割も違います。
しかし、Javaのクラスとしては、どれも最終的に Object につながっています。

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

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

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

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

階層クラス例鬼滅の刃風のイメージ
最上位Objectすべての共通土台
その下DemonSlayer鬼殺隊士
さらに下PillarSlayer
さらに下WaterPillarSlayer水柱

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

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

受け継ぐもの内容
Object のメンバJavaオブジェクトとしての基本機能
DemonSlayer のメンバ鬼殺隊士としての共通機能
PillarSlayer のメンバ柱としての共通機能
WaterPillarSlayer のメンバ水柱としての独自機能

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

鬼滅の刃風にたとえると、水柱は水柱であると同時に、柱でもあり、鬼殺隊士でもあり、Java上では Object につながるオブジェクトでもあるということです。

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

↓クリックすると拡大表示されます。

この図が示していること

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

Object クラスの下に DemonSlayer や NichirinSword があり、DemonSlayer の下に PillarSlayer、さらに WaterPillarSlayer が続いています。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

slayer1.equals(slayer2);

鬼滅の刃風にたとえると、
今見ている隊士と、比べようとしている隊士が同じ存在かどうかを確認する
ようなイメージです。

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

Object 由来の equals は、基本的には同じオブジェクトを指しているかどうかを見る考え方です。
一方で、クラス側で equals をオーバーライドすれば、隊士番号や名前をもとに「同じ隊士かどうか」を判断するようにもできます。

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

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

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

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

slayer1.getClass();

鬼滅の刃風にたとえると、
この隊士は DemonSlayer なのか、PillarSlayer なのか、WaterPillarSlayer なのかを確認する
ような感覚です。

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

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

DemonSlayer slayer1 = new PillarSlayer();

この場合、変数の型は DemonSlayer です。
しかし、実際のオブジェクトは PillarSlayer です。

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

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

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

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

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

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

System.out.println(slayer1);

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

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

鬼滅の刃風にたとえると、隊士が自分の情報を文字で名乗るための基本メソッドです。

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

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

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

DemonSlayer@1a2b3c

これはJavaとしては正しい表示です。
しかし、人間にとっては少し分かりにくいです。

表示印象
DemonSlayer@1a2b3c機械的な識別表示
名前: 水月、階級: 水柱意味が分かりやすい表示

鬼滅の刃風にたとえると、隊士の紹介をしてほしいのに、名前や階級ではなく、識別番号だけが出てくるような感覚です。

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

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

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

たとえば、DemonSlayer クラスなら、名前や階級を分かりやすく返すようにできます。

class DemonSlayer
{
    private String name;
    private String rank;

    public DemonSlayer(String name, String rank)
    {
        this.name = name;
        this.rank = rank;
    }

    public String toString()
    {
        return "名前: " + name + "、階級: " + rank;
    }
}

このように toString を作り直しておくと、次のようにオブジェクトをそのまま出力したときに分かりやすくなります。

DemonSlayer slayer1 = new DemonSlayer("水月", "水柱");
System.out.println(slayer1);

表示例です。

名前: 水月、階級: 水柱

鬼滅の刃風にたとえると、ただの識別コードではなく、隊士が自分の名前と階級をきちんと名乗るようになるイメージです。

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

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

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

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

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

鬼滅の刃風にたとえると、隊士ごとに個性は違います。
柱、隠、継子、日輪刀、任務書など、クラスの種類も違います。

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

図:Object の代表メソッドと共通機能

↓クリックすると拡大表示されます。

この図が示していること

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

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

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

状態表示例
Object 由来の toString をそのまま使うDemonSlayer@1a2b3c
toString をオーバーライドする名前: 水月、階級: 水柱

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

鬼滅の刃風に多重継承とObjectクラスを整理する

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

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

クラス鬼滅の刃風のイメージ
DemonSlayer鬼殺隊士
PillarSlayer
WaterPillarSlayer水柱
NichirinSword日輪刀
MissionScroll任務書

ただし、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 を頂点とした一本の系譜として整理できます。