一番かんたんなJava入門

これからJavaを始めようという人の為の超入門サイトです。丁寧、簡単にこだわった解説なので初心者にぴったりです

【Java】オーバーライドって何?

time 2015/09/06

 あるクラスを継承したクラスを書くと、親クラスのメンバ(メソッド及びフィールド)を全て引き継ぐことが出来るっていうところまで、継承って何?で説明しました。

 その際、親クラスのメソッドを単純に引き継ぐだけでなく、親クラスのメソッドを子クラスで定義しなおす(上書きする)ことも出来ます。親クラスで定義されているメソッドを子クラスで再定義することをオーバーライドと言います。

 よく似たのにオーバーロードっていうのがありますが、全く関係ないので混同しないようにして下さい。
参考:メソッドのオーバーロードって何?

 オーバーライドってどういうことなのかよく分かるように、実際にやってみましょう。

sponsored link

オーバーライドをしてみる

 こんなCarクラスがあるとします。

class Car{
    int gas = 60; //ガソリン

    // gasを消費して走る
    public void drive(int gas){
        this.gas -= gas;
        System.out.println(gas * 10 + "km走りました");
    }

    // gasを補給する
    public void putGas(int gas){
        this.gas += gas;
        System.out.println("ガソリンを" + gas + "リットル補給しました");
    }

    // 現在のgasの値を出力する
    public void checkGas(){
        System.out.println("ガソリンは残り" + this.gas + "リットルです");
    }
}

 フィールドはgasのみ。ガソリンです。60で初期化しています。60リットルと思っていてください。

 メンバメソッドは、

  1. ガソリンを消費して走るdriveメソッド
  2. ガソリンを補給するputGasメソッド
  3. 現在のガソリンの量を出力するcheckGasメソッド

の3つです。

 driveメソッドは、引数に渡したガソリンの量に応じて走るものとします。燃費はリッター当たり10kmということにしておきましょう。フィールドのgasの数値は、使った分だけ減ります。

 putGasメソッドは、引数に渡した数量だけgasが増えます。給油です。

 checkGasメソッドは、現在のgasの値を出力します。

 継承の仕組みにスポットを当てる為、あえてカプセル化は不十分な状態になっています。

 同一パッケージ内のCarTestクラスでこのCarクラスをインスタンス化して走らせてみましょう。

public class CarTest{
    public static void main(String[] args){
        Car car = new Car();
        car.drive(20);
        car.drive(30);
        car.putGas(40);
        car.checkGas();
    }
}

 このCarTestクラスを実行すると、標準出力に以下のように出力されます。

car1

 ここまでは理解できますでしょうか。

 さて、今度はCarクラスを継承したEconomyCarクラスを作ります。EconomyCarクラスは燃費が抜群によくてリッター20km走るとします。

class EconomyCar extends Car{

    @Override
    public void drive(int gas){
        this.gas -= gas;
        System.out.println(gas * 20 + "km走りました");
    }
}

 EconomyCarクラスは、Carクラスを継承しているので、何も書かずともフィールドもメソッドもCarクラスのものを全て引き継いでいます。
 driveメソッドのみ処理内容を再定義することで、リッター20km走るようにしています。このように子クラスにおいて処理内容を再定義することをオーバーライドと言います。

 「@Override」はアノテーションと言って、親クラスのメソッドをオーバーライドすることを宣言する際に付けます。アノテーションを付けることで、もし親クラスに同名のメソッドがなければコンパイラがエラーメッセージを出してくれるので、付けておくことをおすすめします。なくてもプログラム上問題はありません。

 このEconomyCarクラスをインスタンス化して走らせてみましょう。

public class CarTest{
    public static void main(String[] args){
        EconomyCar eCar = new EconomyCar();
        eCar.drive(20);
        eCar.drive(30);
        eCar.putGas(40);
        eCar.checkGas();
    }
}

ecar1

 低燃費が自慢のEconomyCarクラスのインスタンスは、リッター20km走ってますね。

 子クラスのインスタンスメソッドを呼べば、子クラスで再定義(オーバーライド)されているメソッドが呼ばれているのが分かると思います。

オーバーライドの仕組み

 前ページ及びこのページ冒頭で、継承について「親クラスのメンバを引き継ぐ」という表現をしましたが、実際は、継承とは、引き継いでいるというより「親クラスのインスタンスを内包した状態」と言う方が正しいです。

 この考え方は、オーバーライドの仕組みを理解する為に非常に重要です。具体的にどういうことか見ていきましょう。

 Carクラスを継承したEconomyCarクラスのインスタンスの内部構造を図で表すとこんな感じになります。

instance

 EconomyCarインスタンスの中に、Carインスタンスが内包されていて、親子それぞれのクラスのメンバがその中に存在している状態です。EconomyCarクラスのメンバはご覧のようにdriveメソッドだけです。

 一応、メンバの概要だけ再掲しておきます。

class Car{
    int gas = 60;

    public void drive(int gas){
        // 省略
    }

    public void putGas(int gas){
        // 省略
    }

    public void checkGas(){
        // 省略
    }
}

 

class EconomyCar extends Car{

    @Override
    public void drive(int gas){
        // 省略
    }
}

 

 この状態で、EconomyCarクラスのインスタンスメソッドを呼ぶ(及びフィールドにアクセスする)とはどういう状態かと言うと、
instance2
こういうことです。

 子クラスのインスタンスメソッドを外から呼んだ際、そのメソッドが子クラスでオーバーライドされている場合はその子クラスのメソッドが呼ばれます。一方、子クラスでは何ら定義されていないメソッドが呼ばれた場合は、内包している親クラスのメソッドが呼ばれます。

 子クラスにてオーバーライドされた親クラスのメソッドは、隠されたような状態になるわけです。

 これがオーバーライドの基本的な仕組みです。

  • 子クラスのインスタンスは、親クラスのインスタンスを内包している
  • オーバーライドとは親クラスのメソッドを子クラスのメソッドが覆い隠している状態

というのを理解して下さい。

フィールドをオーバーライド?

 この例の場合、オーバーライドされて書き換えられた部分はdriveメソッドの処理中の数字部分(燃費)だけですね。

EconomyCar.java

@Override
public void drive(int gas){
    this.gas -= gas;
    System.out.println(gas * 20 + "km走りました");
}

 これならnenpi(燃費)というフィールドを作って、driveメソッドでそのnenpiの数値を使うようにしておけば、子クラスでnenpiを書き換えるだけで燃費をコントロールすることが出来そうです。

 CarクラスとEconomyCarクラスを以下のようにしてみましょう。

Car.java

class Car{
    int gas = 60; //ガソリン
    int nenpi = 10; //燃費

    // gasを消費して走る
    public void drive(int gas){
        this.gas -= gas;
        System.out.println(gas * this.nenpi + "km走りました");
    }

    // 以下省略
}

 

EconomyCar.java

class EconomyCar extends Car{
    int nenpi = 20;
}

 こうすればEconomyCarのnenpiは20になります。これならわざわざdriveメソッドをオーバーライドせずとも、リッター20km走れるようになるんじゃないでしょうか?

 実行してみましょう。以下のようにCarとEconomyCarを続けて走らせます。

public class CarTest {
    public static void main(String[] args) {
        Car car = new Car();
        car.drive(20);
        car.drive(30);
        car.putGas(40);
        car.checkGas();
        
        System.out.println("");

        EconomyCar eCar = new EconomyCar();
        eCar.drive(20);
        eCar.drive(30);
        eCar.putGas(40);
        eCar.checkGas();
    }
}

 実行結果がこちらです↓
car2

 残念ながらご覧のように、このやり方では低燃費は実現しません。一体、なぜでしょうか??

継承とフィールド

 フィールドも原則的にはメソッドのオーバーライドと同じで、子クラスに同名のフィールドがあればそちらが優先して使われます。先ほどの例に出てきたnenpiフィールドを使った場合は以下のようになります。

instance3

 しかしこの状態だとEconomyCarのnenpiが20に変わっても、EconomyCarの走行距離は2倍にはなりません。

 何故かと言うと、この場合呼ばれるdriveメソッドは親インスタンスのdriveメソッドだからです。

 先ほど説明したように、EconomyCarクラスでdriveメソッドをオーバーライドしていない状態で、EconomyCarインスタンスのdriveメソッドを呼ぶということは、内包されている親インスタンスのdriveメソッドが呼ばれることになります。

instance4

 Carクラスのdriveメソッド内で使われているthis.nenpiは、もちろん自身のインスタンス(Carインスタンス)のnenpiを指します。つまり10です。子クラスがあろうと無かろうと関係ありません。

Car.java

class Car{
    int gas = 60; //ガソリン
    int nenpi = 10; //燃費

    // gasを消費して走る
    public void drive(int gas){
        this.gas -= gas;
        System.out.println(gas * this.nenpi + "km走りました");
    }

    // 以下省略

}

 なので子クラスでフィールドの値をいくつに書き替えていようとも、この状態では全く関係ないということになります。

 親クラスから子クラスのフィールドにアクセスすることは不可能なので、残念ながらこのやり方では低燃費を実現することはできません。

instance5

 子クラスから親クラスのメンバにアクセスする為に用意されているのが、super.○○という書き方です。superに関しては次回に詳しくやります。
 逆に親クラスから子クラスのメンバへはアクセスできません。子クラスにとっては親が存在しないことはありえませんが、親クラスというのは継承されてこそ親となるので、クラス設計の段階で存在するかどうかも分からない子クラスのメンバにアクセスする方法がないのは、ある意味当然と言えます。

 ちなみに親クラスが持つフィールドと同名のフィールドを子クラスで宣言することは、オーバーライドとは言わず、「継承によるフィールドの隠蔽」なんていう風に呼ばれていて、実は推奨されていません(実装すること自体は可能ですが)。

 上記のように、子クラスでフィールドを宣言したところで、親クラスのメソッドを経由してしまうと全くアクセス不能なので、非常に扱いづらくバグの原因となる可能性が高いからです。

 もし非推奨とは言え、無理に子クラスでnenpiフィールドを再宣言するのなら、driveメソッドもオーバーライドして子クラスのdriveメソッドからそのnenpiを触るというやり方にする必要があります。

EconomyCar.java

class EconomyCar extends Car{
    int nenpi = 20; //燃費

    // gasを消費して走る
    public void drive(int gas){
        this.gas -= gas;
        System.out.println(gas * this.nenpi + "km走りました");
    }
}

 これでは子クラスにおいて、親クラスと全く同じ内容のdriveメソッドをオーバーライドしていることになります。

instance6

 結果的にはdriveメソッド内のthisが指し示すインスタンスが変わるので意図通りに動くことは動きますが、どうもやり方が不細工かつ非効率的です。継承を使う意義(親クラスの存在意義)が薄れてしまっています。

 しかしながら、nenpiというパラメータ(フィールド)を用意することで、燃費をコントロールするというやり方自体は、設計として決して悪いものではないと思います。

 次回は、なんとかしてnenpiフィールドを取り入れた設計を完成させてみましょう。superを使います。

分からないことはプロのエンジニアに聞いてみてください↓

sponsored link

Androidアプリを作ろう

コメント

  • そうですね。コンストラクタを書いてなかったらデフォルトコンストラクタが動いているってことです。
    継承とコンストラクタについても書いてますのでまた読んでみてください。

    by Nobuo@管理人 €2017/04/10 19:54

down

コメントする



一番かんたんなJava入門

Androidアプリの作り方

忘備録

私の作ったAndroidアプリ

おすすめ入門書・サービス

おすすめプログラミングスクール

 この本は全く何も分からない初心者の方にお勧めです。プログラミングをするには覚えなければならない事が無茶苦茶いっぱいありますが、この本は教えてくれる順番、その構成が素晴らしいです。RPGのゲームを作るというストーリーにのっとってちょっとずつ難しいことを教えてもらえます。
 無機質で膨大なデータが載っているような本は読む気にならないという方は、こういうストーリー仕立ての本でチャレンジしてみてはいかがでしょうか?(注:RPGを作る為の本ではありません。)

 ある程度、Javaを読み書きできるようになったら、オブジェクト指向について学ぶべきです。本書は、抽象的で分かったような分からんようなオブジェクト指向という考え方について、非常に分かりやすい例を出して説明してくれています。オブジェクト指向とは何なのか?という本質を掴むのにこれほど適した本はないと思います。
 オブジェクト志向の理念を理解できれば、より効率のいいコードをより楽に書けるようになるはずです。Java上級者を目指すなら必読の一冊![詳細]

フリーランスで稼ぎたい人へ

管理人

Nobuo_CREATE

Nobuo_CREATE

WordPressテーマPrinciple、マテリアルを作ったり、Androidアプリを作ったり、Java入門サイトを作ったり、本を書いたりしています。どうぞよろしく。 [詳細]



sponsored link

オススメ書籍

 この本は全く何も分からない初心者の方にお勧めです。プログラミングをするには覚えなければならない事が無茶苦茶いっぱいありますが、この本は教えてくれる順番、その構成が素晴らしいです。RPGのゲームを作るというストーリーにのっとってちょっとずつ難しいことを教えてもらえます。
 無機質で膨大なデータが載っているような本は読む気にならないという方は、こういうストーリー仕立ての本でチャレンジしてみてはいかがでしょうか?(注:RPGを作る為の本ではありません。)

 ある程度、Javaを読み書きできるようになったら、オブジェクト指向について学ぶべきです。本書は、抽象的で分かったような分からんようなオブジェクト指向という考え方について、非常に分かりやすい例を出して説明してくれています。オブジェクト指向とは何なのか?という本質を掴むのにこれほど適した本はないと思います。
 オブジェクト志向の理念を理解できれば、より効率のいいコードをより楽に書けるようになるはずです。Java上級者を目指すなら必読の一冊![詳細]

只今、急拡大中

JavaからのRuby on Rails入門

JavaからのRuby on Rails入門

大人気!COBOLからのJAVA

国内の仮想通貨 取引所まとめ

【仮想通貨を始めたい人必見】日本国内の全取引所まとめ&オススメ