Java道|メソッドのオーバーライド

同じ技名でも、使う隊士が変われば中身も変わる。
オーバーライドを理解すると、継承は「受け継ぐ」だけでなく「自分流に作り直す」仕組みだと見えてきます。

継承を学ぶと、スーパークラスで用意したフィールドやメソッドを、サブクラスが受け継いで使えることが分かります。

たとえば、鬼殺隊士を表す DemonSlayer クラスに show() というメソッドがあるとします。
この show() は、隊士の名前や階級を表示する共通メソッドです。

しかし、そこから派生した PillarSlayer クラスでは、名前や階級だけでなく、担当区域も表示したいかもしれません。

このようなとき、サブクラス側で、スーパークラスと同じ名前・同じ引数のメソッドをもう一度定義できます。
すると、サブクラスのオブジェクトに対してそのメソッドを呼び出したとき、親クラスのメソッドではなく、子クラスで定義したメソッドが使われます。

これが メソッドのオーバーライド です。

鬼滅の刃風にたとえると、鬼殺隊士には共通の自己紹介があります。
でも、柱には柱としての紹介があります。

クラスshow() のイメージ
DemonSlayer鬼殺隊士として名前と階級を表示する
PillarSlayer柱として名前、階級、担当区域を表示する

同じ show() という名前でも、PillarSlayer のオブジェクトで呼び出せば、柱用に作り直した show() が動きます。
つまりオーバーライドは、親クラスの共通機能を、子クラスの個性に合わせて上書きする仕組み です。

オーバーライドとは何か

オーバーライドとは、スーパークラスにあるメソッドと同じ名前、同じ引数の形を持つメソッドを、サブクラス側で定義しなおすことです。

そして、サブクラスのオブジェクトに対してそのメソッドを呼び出すと、スーパークラス側のメソッドではなく、サブクラス側で定義したメソッドが実行されます。

たとえば、親クラスに次のようなメソッドがあるとします。

public void show()
{
    System.out.println("隊士の情報を表示します。");
}

サブクラス側で、同じ show() を定義します。

public void show()
{
    System.out.println("柱の情報を表示します。");
}

このように、メソッド名と引数の形が同じであれば、サブクラス側のメソッドは親クラスのメソッドをオーバーライドします。

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

鬼滅の刃風にたとえると、DemonSlayer クラスには「隊士としての基本紹介」があります。
でも、PillarSlayer クラスでは「柱としての詳しい紹介」に作り直します。

つまり、

考え方内容
親クラス共通のメソッドを用意する
子クラス同じメソッドを自分用に作り直す
呼び出し時子クラスのオブジェクトなら子クラス版が動く

という仕組みです。

同じメソッド名なら何でもオーバーライドになるのか

ここで大切なのは、ただ同じ名前を書けばよいわけではないという点です。

オーバーライドとして成立するには、スーパークラスとサブクラスで次の形が一致している必要があります。

条件内容
メソッド名同じ名前にする
引数の数同じ数にする
引数の型同じ型にする

たとえば、親クラスに show() がある場合、子クラスでも show() と同じ形で定義します。

public void show()

このように同じ形で定義すると、Javaは「これは新しい別メソッドではなく、親の show() を子クラス用に作り直したものだ」と判断します。

一方で、次のように引数が付くと、オーバーライドではなく別のメソッドになります。

public void show(String message)

これは show という名前は同じですが、引数の形が違います。
そのため、親の show() を上書きしているのではなく、別の呼び出し方を持つ show(String message) になります。

継承したメソッドをそのまま使う場合との違い

継承では、スーパークラスのメソッドをサブクラスからそのまま使えます。

もし PillarSlayer クラスに show() を書かなければ、DemonSlayer の show() が使われます。

しかし、それでは足りない場面があります。

たとえば、DemonSlayer の show() が次の情報だけを表示するとします。

表示内容説明
name隊士の名前
rank階級

でも、PillarSlayer では次の情報も表示したいかもしれません。

追加で表示したい内容説明
area担当区域

このようなとき、PillarSlayer 側で show() を定義しなおします。

つまり、オーバーライドを使うと、

通常の継承オーバーライド
親のメソッドをそのまま使う親と同じ形のメソッドを子で作り直す
共通処理を使える子クラスに合った処理に変えられる
追加情報を表示しにくい場合がある追加情報も含めて表示できる

という違いがあります。

鬼滅の刃風に考えるオーバーライド

鬼滅の刃風にたとえると、DemonSlayer クラスには鬼殺隊士としての基本紹介があります。

たとえば、

DemonSlayer の紹介内容
名前隊士名を表示
階級階級を表示

これは鬼殺隊士全体に共通する基本的な紹介です。

しかし、PillarSlayer クラスでは、柱としての情報も必要になります。

PillarSlayer の紹介内容
名前隊士名を表示
階級階級を表示
担当区域守る区域を表示

このとき、メソッド名は show() のままで、中身だけを PillarSlayer 用に作り直します。

同じ「情報を表示する」という役割を持ちながら、クラスごとに中身を変える。
これがオーバーライドの自然な考え方です。

オーバーライドを確認する

ファイル名:Sample4.java

class DemonSlayer
{
    protected String name;
    protected String rank;

    public DemonSlayer()
    {
        name = "隊士未登録";
        rank = "階級未設定";
        System.out.println("鬼殺隊士を作成しました。");
    }

    public void setSlayer(String n, String r)
    {
        name = n;
        rank = r;
        System.out.println("名前を" + name + "に、階級を" + rank + "にしました。");
    }

    public void show()
    {
        System.out.println("隊士の名前は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
    }
}

class PillarSlayer extends DemonSlayer
{
    private int area;

    public PillarSlayer()
    {
        area = 0;
        System.out.println("柱クラスの隊士を作成しました。");
    }

    public void setArea(int a)
    {
        area = a;
        System.out.println("担当区域を" + area + "にしました。");
    }

    public void show()
    {
        System.out.println("柱の名前は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
        System.out.println("担当区域は" + area + "です。");
    }
}

class Sample4
{
    public static void main(String[] args)
    {
        PillarSlayer slayer1;
        slayer1 = new PillarSlayer();

        slayer1.setSlayer("水月", "水柱");
        slayer1.setArea(7);

        slayer1.show();
    }
}

Sample4.java で注目する show() の関係

このプログラムでは、DemonSlayer にも PillarSlayer にも show() があります。

クラスshow() の役割
DemonSlayer名前と階級を表示する
PillarSlayer名前、階級、担当区域を表示する

DemonSlayer の show() は次の部分です。

public void show()
{
    System.out.println("隊士の名前は" + name + "です。");
    System.out.println("階級は" + rank + "です。");
}

PillarSlayer の show() は次の部分です。

public void show()
{
    System.out.println("柱の名前は" + name + "です。");
    System.out.println("階級は" + rank + "です。");
    System.out.println("担当区域は" + area + "です。");
}

どちらもメソッド名は show です。
引数もありません。

つまり、PillarSlayer の show() は、DemonSlayer の show() をオーバーライドしています。

なぜサブクラス側の show() が呼ばれるのか

main メソッドでは、最後に次のように書いています。

slayer1.show();

slayer1 は PillarSlayer のオブジェクトです。

slayer1 = new PillarSlayer();

そのため、slayer1.show() を呼び出すと、DemonSlayer の show() ではなく、PillarSlayer の show() が呼び出されます。

Javaは、PillarSlayer のオブジェクトに対して show() が呼ばれているので、子クラス側で定義された show() を優先します。

鬼滅の刃風にたとえると、鬼殺隊士全体の基本紹介は親クラスにあります。
しかし、水柱として作られた隊士なら、水柱としての詳しい紹介をするのが自然です。

つまり、

呼び出し実際に動くメソッド
slayer1.show()PillarSlayer の show()

となります。

親の show() と子の show() の違い

DemonSlayer の show() と PillarSlayer の show() を比べると、次のようになります。

比較項目DemonSlayer の show()PillarSlayer の show()
メソッド名show()show()
引数なしなし
表示する情報名前、階級名前、階級、担当区域
役割鬼殺隊士としての基本表示柱としての詳しい表示

ここで大切なのは、メソッド名と引数の形は同じなのに、中身が違うことです。

子クラスは、親クラスのメソッドの役割を受け継ぎながら、自分に合った内容へ作り直しています。

これが、オーバーライドの「上書き」という感覚です。

オーバーライドは親を消すことではない

オーバーライドという言葉を聞くと、親クラスのメソッドが消えてしまうように感じるかもしれません。

しかし、親クラスの show() がなくなるわけではありません。
DemonSlayer には DemonSlayer の show() が残っています。

ただ、PillarSlayer のオブジェクトに対して show() を呼び出したときは、PillarSlayer の show() が使われるということです。

誤解正しい理解
親のメソッドが消える親のメソッドは残る
子が親を完全に壊す子クラス用の振る舞いに切り替わる
同じ名前だから混乱するオブジェクトの種類に合ったメソッドが選ばれる

鬼滅の刃風にたとえると、鬼殺隊士としての基本紹介は存在しています。
ただし、柱として紹介するときには、柱用の紹介文を使うということです。

protected がここで役立っている理由

Sample4.java では、DemonSlayer の name と rank が protected になっています。

protected String name;
protected String rank;

これにより、サブクラスである PillarSlayer の show() から、name と rank を直接使えます。

System.out.println("柱の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");

もし name と rank が private だった場合、PillarSlayer の show() から直接アクセスできません。

つまり、今回のオーバーライドは、protected の仕組みとも関係しています。

フィールドアクセス修飾子PillarSlayer から直接使えるか
nameprotected使える
rankprotected使える
areaprivate同じ PillarSlayer クラス内なので使える

PillarSlayer の show() は、親クラスの protected メンバと、自分の private メンバを組み合わせて表示しています。

実行の流れを整理する

Sample4.java の処理の流れは次のようになります。

順番処理内容
1PillarSlayer slayer1;PillarSlayer 型の変数を用意する
2slayer1 = new PillarSlayer();PillarSlayer オブジェクトを作成する
3DemonSlayer()親クラスのコンストラクタが先に動く
4PillarSlayer()子クラスのコンストラクタが動く
5slayer1.setSlayer("水月", "水柱");親クラスのメソッドで名前と階級を設定する
6slayer1.setArea(7);子クラスのメソッドで担当区域を設定する
7slayer1.show();子クラスでオーバーライドした show() が呼ばれる

特に重要なのは、最後の slayer1.show() です。

show() という同じ呼び出しでも、slayer1 が PillarSlayer のオブジェクトなので、PillarSlayer の show() が実行されます。

実行結果の例

鬼殺隊士を作成しました。
柱クラスの隊士を作成しました。
名前を水月に、階級を水柱にしました。
担当区域を7にしました。
柱の名前は水月です。
階級は水柱です。
担当区域は7です。

最初に、オブジェクト作成時のメッセージが表示されます。

鬼殺隊士を作成しました。
柱クラスの隊士を作成しました。

これは、継承のルールにより、先に DemonSlayer のコンストラクタが動き、そのあとに PillarSlayer のコンストラクタが動くためです。

次に、名前、階級、担当区域を設定します。

名前を水月に、階級を水柱にしました。
担当区域を7にしました。

最後に show() を呼び出すと、PillarSlayer 側でオーバーライドした show() が動きます。

そのため、名前と階級だけでなく、担当区域も表示されます。

図:オーバーライドの基本構造

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

この図が示していること

この図では、DemonSlayer と PillarSlayer の両方に show() があることを表しています。

DemonSlayer の show() は、名前と階級を表示する基本的なメソッドです。
PillarSlayer の show() は、名前、階級、担当区域を表示するメソッドです。

どちらも同じ show() ですが、PillarSlayer 側で同じ形の show() を定義しなおしているため、オーバーライドになっています。

右側の slayer1.show() から PillarSlayer の show() に矢印が向いているところが重要です。

これは、slayer1 が PillarSlayer のオブジェクトなので、子クラス版の show() が呼ばれることを示しています。

オーバーライドがあると何が便利なのか

オーバーライドの良さは、共通のメソッド名を使いながら、クラスごとにふさわしい処理を持たせられることです。

たとえば show() という名前を共通にしておけば、どのクラスでも「情報を表示する」という役割だと分かりやすくなります。

そのうえで、表示する中身はクラスごとに変えられます。

良い点内容
共通の名前で扱えるshow() と書けば情報表示だと分かる
中身は個別に変えられるクラスごとの特徴を表示できる
継承と相性がよい親の共通性と子の個性を両立できる
コードの意図が分かりやすい役割は同じ、処理内容はクラスごとに違うと整理できる

鬼滅の刃風にたとえると、どの隊士にも自己紹介があります。
ただし、一般隊士の自己紹介と柱の自己紹介では、表示したい情報が違います。

オーバーライドを使えば、どちらも show() という同じ名前で表しながら、それぞれに合った内容を表示できます。

図:親の show() と子の show() の違い

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

この図が示していること

この図では、親クラスの show() と子クラスの show() の違いを比較しています。

DemonSlayer の show() は、隊士の名前と階級を表示します。
PillarSlayer の show() は、それに加えて担当区域も表示します。

どちらも show() という同じ名前のメソッドです。
しかし、PillarSlayer では柱として必要な情報を追加しているため、処理内容が変わっています。

この図から分かるのは、オーバーライドは単なる名前の重複ではなく、同じ役割のメソッドを子クラスに合わせて作り直す仕組み だということです。

オーバーライドは多態性につながる土台

今回の中心は、PillarSlayer のオブジェクトで show() を呼ぶと、PillarSlayer の show() が動くという点です。

この考え方は、あとで学ぶポリモーフィズムにもつながります。

ポリモーフィズムでは、同じメソッド呼び出しでも、実際のオブジェクトによって動きが変わります。
その土台になる代表的な仕組みがオーバーライドです。

鬼滅の刃風にたとえると、司令部が「情報を見せて」と同じ命令を出しても、一般隊士なら一般隊士として、柱なら柱として、自分に合った情報を見せるようなイメージです。

仕組み内容
オーバーライド子クラスで親のメソッドを作り直す
ポリモーフィズム同じ呼び出しで、実際のオブジェクトごとに違う処理が動く

このように、オーバーライドは単なる上書きではありません。
共通の呼び出し方と、個別の振る舞いを両立させるための大切な仕組みです。

@Override を付けるとミスに気づきやすい

Javaでは、オーバーライドするメソッドの前に @Override を付けることがあります。

たとえば、Sample4.java の PillarSlayer の show() は、次のように書くこともできます。

@Override
public void show()
{
    System.out.println("柱の名前は" + name + "です。");
    System.out.println("階級は" + rank + "です。");
    System.out.println("担当区域は" + area + "です。");
}

@Override を付けると、Javaに対して「このメソッドは親クラスのメソッドをオーバーライドするつもりです」と伝えられます。

もしメソッド名や引数を間違えていた場合、コンパイル時に気づきやすくなります。

たとえば、show() と書くつもりで sho() と書いてしまうと、オーバーライドになりません。

@Override
public void sho()
{
    System.out.println("柱の情報を表示します。");
}

このような場合、@Override が付いていれば、Javaが「親クラスに対応するメソッドがありません」と教えてくれます。

そのため、実際の開発では @Override を付ける習慣を持つと安全です。

鬼滅の刃風にオーバーライドを整理する

DemonSlayer は、鬼殺隊士としての共通の土台です。
そこには show() という基本的な情報表示メソッドがあります。

PillarSlayer は、DemonSlayer を受け継いだ柱の隊士です。
柱には担当区域という追加情報があります。

そのため、PillarSlayer では show() を同じ名前のまま作り直し、柱に必要な情報まで表示するようにしています。

用語鬼滅の刃風のイメージ
スーパークラス鬼殺隊士の共通設計
サブクラス柱として拡張された隊士
show()情報を表示する共通の技
オーバーライド同じ技名のまま、自分用に技の中身を作り直す

ひとことで言えば、オーバーライドは、
親の技の型を受け継ぎながら、子が自分の戦い方に合わせて磨き直すこと
です。

この感覚がつかめると、オーバーライドは文法の暗記ではなく、継承の中で子クラスらしさを表現するための自然な仕組みとして理解しやすくなります。

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

ポイント内容
オーバーライド親クラスのメソッドを子クラスで作り直すこと
条件メソッド名、引数の数、引数の型を同じにする
呼び出し子クラスのオブジェクトなら子クラス版が呼ばれる
親のメソッド消えるわけではなく、子クラスの呼び出しでは子版が優先される
protected子クラスのオーバーライド内で親の情報を使いたいときに役立つ
@Overrideオーバーライドのつもりで書いたことを明示できる
多態性との関係同じ呼び出しで違う動きをする考え方につながる

オーバーライドを理解すると、継承は単に親の機能を受け継ぐだけではないことが分かります。

親クラスに共通の形を用意し、子クラスで自分に合った中身へ作り直す。
この考え方が、Javaのオブジェクト指向をより柔軟で分かりやすいものにしてくれます。