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

同じ show() でも、普通のサイヤ人戦士とスーパーサイヤ人戦士では見せたい情報が変わります。オーバーライドを理解すると、継承は「受け継ぐ」だけでなく「子クラス用に作り直す」仕組みだと分かります。

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

たとえば、サイヤ人戦士を表す SaiyanWarrior クラスに show() というメソッドがあるとします。

この show() は、戦士の名前や流派を表示する共通メソッドです。

しかし、そこから派生した SuperSaiyanWarrior クラスでは、名前や流派だけでなく、変身段階も表示したい場面があります。

このようなとき、サブクラス側で、スーパークラスと同じ名前・同じ引数のメソッドをもう一度定義できます。

すると、サブクラスのオブジェクトに対してそのメソッドを呼び出したとき、親クラスのメソッドではなく、子クラスで定義したメソッドが使われます。

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

ドラゴンボールの世界観でたとえると、サイヤ人戦士には共通の自己紹介があります。

でも、スーパーサイヤ人戦士には、スーパーサイヤ人としての自己紹介があります。

クラスshow() のイメージ
SaiyanWarriorサイヤ人戦士として名前と流派を表示する
SuperSaiyanWarriorスーパーサイヤ人として名前、流派、変身段階を表示する

同じ show() という名前でも、SuperSaiyanWarrior のオブジェクトで呼び出せば、スーパーサイヤ人用に作り直した show() が動きます。

つまりオーバーライドは、親クラスの共通機能を、子クラスの個性に合わせて上書きする仕組みです。

この記事では、具体的なプログラムとして Sample4.java を使いながら、メソッドのオーバーライド、同じメソッド名でも子クラス版が優先される理由、protected メンバとの関係、@Override の役割を、ドラゴンボールの世界観で解説します。

オーバーライドとは何か

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

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

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

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

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

public void show()
{
    System.out.println("スーパーサイヤ人戦士の情報を表示します。");
}

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

ドラゴンボール風にたとえると、SaiyanWarrior クラスにはサイヤ人戦士としての基本紹介があります。

でも、SuperSaiyanWarrior クラスでは、スーパーサイヤ人としての詳しい紹介に作り直します。

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

この仕組みによって、同じ show() という名前を使いながら、クラスごとにふさわしい表示内容を持たせることができます。

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

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

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

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

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

public void show()

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

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

public void show(String message)

これは show という名前は同じですが、引数の形が違います。

そのため、親の show() を上書きしているのではなく、別の呼び出し方を持つ show(String message) になります。

子クラス側の書き方親の show() をオーバーライドするか
public void show()する
public void show(String message)しない
public void show(int level)しない

つまり、オーバーライドでは、名前だけでなく、引数の形も合わせる必要があります。

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

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

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

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

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

表示内容説明
name戦士の名前
styleName流派名

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

追加で表示したい内容説明
transformationLevel変身段階

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

つまり、オーバーライドを使うと、次のような違いが出ます。

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

ドラゴンボール風に考えると、普通のサイヤ人戦士なら名前と流派だけで十分かもしれません。

しかし、スーパーサイヤ人戦士を紹介するなら、変身段階も表示したほうが自然です。

そこで、show() という名前はそのままにして、中身をスーパーサイヤ人用に作り直します。

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

この図が示していること

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

SaiyanWarrior の show() は、名前と流派を表示する基本的なメソッドです。

SuperSaiyanWarrior の show() は、名前と流派に加えて、変身段階も表示するメソッドです。

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

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

ドラゴンボール風に考えるオーバーライド

ドラゴンボール風にたとえると、SaiyanWarrior クラスにはサイヤ人戦士としての基本紹介があります。

たとえば、次の情報を表示します。

SaiyanWarrior の紹介内容
名前戦士名を表示
流派亀仙流、惑星戦士流、神流などを表示

これはサイヤ人戦士全体に共通する基本的な紹介です。

しかし、SuperSaiyanWarrior クラスでは、スーパーサイヤ人としての情報も必要になります。

SuperSaiyanWarrior の紹介内容
名前戦士名を表示
流派流派名を表示
変身段階スーパーサイヤ人としての段階を表示

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

同じ「情報を表示する」という役割を持ちながら、クラスごとに中身を変える。

これがオーバーライドの自然な考え方です。

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

ファイル名:Sample4.java

class SaiyanWarrior
{
    protected String name;
    protected String styleName;

    public SaiyanWarrior()
    {
        name = "戦士未登録";
        styleName = "流派未設定";
        System.out.println("サイヤ人戦士を作成しました。");
    }

    public void setWarrior(String n, String s)
    {
        name = n;
        styleName = s;
        System.out.println("名前を" + name + "に、流派を" + styleName + "にしました。");
    }

    public void show()
    {
        System.out.println("戦士の名前は" + name + "です。");
        System.out.println("流派は" + styleName + "です。");
    }
}

class SuperSaiyanWarrior extends SaiyanWarrior
{
    private int transformationLevel;

    public SuperSaiyanWarrior()
    {
        transformationLevel = 0;
        System.out.println("スーパーサイヤ人クラスの戦士を作成しました。");
    }

    public void setTransformationLevel(int level)
    {
        transformationLevel = level;
        System.out.println("変身段階を" + transformationLevel + "にしました。");
    }

    public void show()
    {
        System.out.println("スーパーサイヤ人の名前は" + name + "です。");
        System.out.println("流派は" + styleName + "です。");
        System.out.println("変身段階は" + transformationLevel + "です。");
    }
}

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

        warrior1.setWarrior("悟空", "亀仙流");
        warrior1.setTransformationLevel(1);

        warrior1.show();
    }
}

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

クラスshow() の役割
SaiyanWarrior名前と流派を表示する
SuperSaiyanWarrior名前、流派、変身段階を表示する

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

public void show()
{
    System.out.println("戦士の名前は" + name + "です。");
    System.out.println("流派は" + styleName + "です。");
}

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

public void show()
{
    System.out.println("スーパーサイヤ人の名前は" + name + "です。");
    System.out.println("流派は" + styleName + "です。");
    System.out.println("変身段階は" + transformationLevel + "です。");
}

どちらもメソッド名は show です。

引数もありません。

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

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

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

warrior1.show();

warrior1 は SuperSaiyanWarrior のオブジェクトです。

warrior1 = new SuperSaiyanWarrior();

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

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

ドラゴンボール風にたとえると、サイヤ人戦士全体の基本紹介は親クラスにあります。

しかし、悟空がスーパーサイヤ人として作られた戦士なら、スーパーサイヤ人としての詳しい紹介をするのが自然です。

つまり、次のようになります。

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

同じ show() という呼び出しでも、オブジェクトが SuperSaiyanWarrior なので、子クラス版が動きます。

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

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

比較項目SaiyanWarrior の show()SuperSaiyanWarrior の show()
メソッド名show()show()
引数なしなし
表示する情報名前、流派名前、流派、変身段階
役割サイヤ人戦士としての基本表示スーパーサイヤ人としての詳しい表示

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

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

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

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

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

しかし、親クラスの show() がなくなるわけではありません。

SaiyanWarrior には SaiyanWarrior の show() が残っています。

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

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

ドラゴンボール風にたとえると、サイヤ人戦士としての基本紹介は存在しています。

ただし、スーパーサイヤ人として紹介するときには、スーパーサイヤ人用の紹介文を使うということです。

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

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

protected String name;
protected String styleName;

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

System.out.println("スーパーサイヤ人の名前は" + name + "です。");
System.out.println("流派は" + styleName + "です。");

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

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

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

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

図:子クラスの show() が呼ばれる流れ

この図が示していること

この図では、warrior1.show() を呼び出したときに、SuperSaiyanWarrior の show() が実行される流れを表しています。

warrior1 は new SuperSaiyanWarrior() で作られたオブジェクトです。

そのため、show() を呼び出すと、親クラスである SaiyanWarrior の show() ではなく、子クラスでオーバーライドした SuperSaiyanWarrior の show() が優先されます。

ただし、親クラスの show() が消えるわけではありません。

親クラスの show() は SaiyanWarrior 側に残っています。

この図から分かるのは、オーバーライドでは、同じメソッド名でも、実際のオブジェクトに合ったメソッドが選ばれるということです。

実行の流れを整理する

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

順番処理内容
1SuperSaiyanWarrior warrior1;SuperSaiyanWarrior 型の変数を用意する
2warrior1 = new SuperSaiyanWarrior();SuperSaiyanWarrior オブジェクトを作成する
3SaiyanWarrior()親クラスのコンストラクタが先に動く
4SuperSaiyanWarrior()子クラスのコンストラクタが動く
5warrior1.setWarrior("悟空", "亀仙流");親クラスのメソッドで名前と流派を設定する
6warrior1.setTransformationLevel(1);子クラスのメソッドで変身段階を設定する
7warrior1.show();子クラスでオーバーライドした show() が呼ばれる

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

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

実行結果の例

サイヤ人戦士を作成しました。
スーパーサイヤ人クラスの戦士を作成しました。
名前を悟空に、流派を亀仙流にしました。
変身段階を1にしました。
スーパーサイヤ人の名前は悟空です。
流派は亀仙流です。
変身段階は1です。

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

サイヤ人戦士を作成しました。
スーパーサイヤ人クラスの戦士を作成しました。

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

次に、名前、流派、変身段階を設定します。

名前を悟空に、流派を亀仙流にしました。
変身段階を1にしました。

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

そのため、名前と流派だけでなく、変身段階も表示されます。

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

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

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

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

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

ドラゴンボール風にたとえると、どの戦士にも自己紹介があります。

ただし、普通のサイヤ人戦士の自己紹介と、スーパーサイヤ人戦士の自己紹介では、表示したい情報が違います。

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

親の show() と子の show() の違いを整理する

親クラスの show() と子クラスの show() は、同じ名前でも役割の細かさが違います。

観点SaiyanWarrior の show()SuperSaiyanWarrior の show()
共通性すべてのサイヤ人戦士に共通スーパーサイヤ人戦士に特化
表示内容名前、流派名前、流派、変身段階
使う場面通常の戦士情報を見たいとき変身段階まで見たいとき
継承との関係親の共通メソッド子で作り直したメソッド

このように見ると、オーバーライドは単なる上書きではありません。

同じ目的を持つメソッドを、子クラスの性質に合わせてより具体的にする仕組みです。

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

この図が示していること

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

SaiyanWarrior の show() は、戦士の名前と流派を表示します。

SuperSaiyanWarrior の show() は、それに加えて変身段階も表示します。

どちらも show() という同じ名前のメソッドです。

しかし、SuperSaiyanWarrior ではスーパーサイヤ人として必要な情報を追加しているため、処理内容が変わっています。

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

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

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

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

ポリモーフィズムでは、同じメソッド呼び出しでも、実際のオブジェクトによって動きが変わります。

その土台になる代表的な仕組みがオーバーライドです。

ドラゴンボール風にたとえると、戦士管理本部が「情報を見せて」と同じ命令を出しても、通常のサイヤ人戦士なら通常の戦士として、スーパーサイヤ人戦士ならスーパーサイヤ人として、自分に合った情報を見せるようなイメージです。

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

このように、オーバーライドは単なる上書きではありません。

共通の呼び出し方と、個別の振る舞いを両立させるための大切な仕組みです。

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

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

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

@Override
public void show()
{
    System.out.println("スーパーサイヤ人の名前は" + name + "です。");
    System.out.println("流派は" + styleName + "です。");
    System.out.println("変身段階は" + transformationLevel + "です。");
}

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

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

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

@Override
public void sho()
{
    System.out.println("スーパーサイヤ人の情報を表示します。");
}

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

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

@Override を付ける利点内容
タイプミスに気づきやすいshow を sho と書いた場合など
引数違いに気づきやすいshow(String message) になっていた場合など
意図が伝わりやすいこのメソッドはオーバーライドだと分かる
保守しやすい後から読んだ人にも分かりやすい

ドラゴンボール風にオーバーライドを整理する

SaiyanWarrior は、サイヤ人戦士としての共通の土台です。

そこには show() という基本的な情報表示メソッドがあります。

SuperSaiyanWarrior は、SaiyanWarrior を受け継いだスーパーサイヤ人戦士です。

スーパーサイヤ人には、変身段階という追加情報があります。

そのため、SuperSaiyanWarrior では show() を同じ名前のまま作り直し、スーパーサイヤ人に必要な情報まで表示するようにしています。

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

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

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

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

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

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

親クラスに共通の形を用意し、子クラスで自分に合った中身へ作り直す。

この考え方が、Javaのオブジェクト指向をより柔軟で分かりやすいものにしてくれます。