Javaプログラミング入門 12章 [継承] Part2

Java

こんにちは。ECF Techブログ
担当 Michiharu.Tです。

Javaプログラミング入門記事の第12章をお送りいたします。
第12章のテーマは「継承」です。
第12章は2回のパートに分けて掲載します。今回はPart2です。

本連載の初回および章立ての一覧については下記のリンクから確認できます。

Javaプログラミング入門 0章
0章 Javaプログラミングを始めよう こんにちは。ECF Techブログ 担当 Michiharu.Tです。 この記事はプログラミング言語のJavaを、実際に動かしながら学びたい人のための学習教材となっています。手を多く動かすこと、感覚を...

12-4 メソッドのオーバーライド

ここではメソッドのオーバーライドのしくみを使って、プリペイドカードのもう1つの機能である下の機能を追加したいと思います。

「月の利用回数が一定数になると、以降の支払の都度、20ポイントのボーナスが付与される。」

12-4-1 オーバーライドとは

オーバーライド(override) は、親クラスから継承されたメソッドを子クラスで定義しなおすしくみです。

メソッドをオーバーライドしたPrepaidCardクラスのプログラムを示します。親クラスのPointCard.javaも再掲します。

プログラム例(PointCard.java)

public class PointCard {
    public int id;      //カード番号
    public int point;   //残ポイント

    //コンストラクタ
    public PointCard(int id, int point){
        this.id = id;
        this.point = point;
    }

    //支払処理
    public void payment(int price){
        //金額の1%をポイントに加算
        point += price*0.01;
    }
}

プログラム例(PrepaidCard.java)

public class PrepaidCard extends PointCard{
    public int monthlyUse;  //月の利用回数

    //コンストラクタ
    public PrepaidCard(int id, int point){
        //親クラスのコンストラクタを呼び出す
        super(id, point);
        this.monthlyUse = 0;
    }

    //オーバーライドしたメソッド
    public void payment(int price) {
        point += price*0.01;
        //利用回数を加算
        monthlyUse++;
        if( monthlyUse >= 2 ){
            //月2回以上の利用でボーナス
            point += 20;
        }
    }

    //現金からチャージするメソッド
    public void charge(int cash){
        point += cash;
    }
}

解説
オーバーライドの方法は簡単です。図のように親クラスのメソッド宣言部分と全く同じ宣言を行ない、処理部分(ブロック内部)のみを変更します。

なお、オーバーライドしたpaymentメソッドでは次の順番で処理を行っています。

  • 引数priceの1%をpointフィールドに加算。
  • 月の利用回数(monthlyUseフィールド)を1加算。
  • 月の利用回数が2以上であれば、ボーナスとして20ポイントをpointフィールドに加算。

12-4-2 オーバーライドしたメソッドの動作

それでは、オーバーライドしたメソッドがどのように動作するかを確認してみましょう。下のメインクラスを使って動作を確認します。

プログラム例(Main.java)

public class Main{
    public static void main(String[] args){
        //PrepaidCardインスタンスの生成
        PrepaidCard card = new PrepaidCard(2,50);
        //支払前のポイント残高
        System.out.println(card.point);
        //1回目の支払い
        card.payment(1000);
        System.out.println(card.point);
        //2回目の支払い
        card.payment(1000);
        System.out.println(card.point);
    }
}

実行結果

50
60
90

解説
始めに4行目でPrepaidCardインスタンスを生成しています。コンストラクタ経由でpointフィールドに50を代入しているため、次の行でcard.pointを表示すると50が表示されます。

以降、card.payment(1000);でpaymentメソッドを呼び出してpointフィールドの値を表示する処理を2回行なっています。1回目のメソッド呼び出し後は、ポイントが10ポイント増えている(実行結果が5060)ことがわかります。

一方で2回目のメソッド呼び出し後は、ポイントが30ポイント増えています(実行結果が6090)。つまり、各paymentメソッドの呼び出しで実際に実行されているのは、PrepaidCardクラスでオーバーライドしたpaymentメソッドであることがわかります(下図)。

12-5 親クラスのメソッド呼び出し

12-4で親クラスから継承されたメソッドをオーバーライドしました。ですが、上の図にもあるとおり、これにより親クラスから継承されたメソッドが無くなったわけではありません。

ここでは、元々の親クラスのメソッドを呼び出す方法を紹介しながら、PrepaidCardクラスのプログラム改善を行います。

始めに改善箇所の説明です。12-4で動作させたPointCardクラスとPrepaidCardクラスのpaymentメソッドを比較すると、下記の部分が共通していることがわかります。

重複する処理は保守性を低下させてしまう元なので、この点を親クラスのメソッドを呼び出す方法を使って改善します。

子クラスのメソッドから、親クラスのメソッドを呼び出す文法は次のとおりです。

super.メソッド名(引数...);

それでは、改善したPrepaidCardクラスのプログラムを示します。

プログラム例(PrepaidCard.java)

public class PrepaidCard extends PointCard{
    public int monthlyUse;  //月の利用回数

    //コンストラクタ
    public PrepaidCard(int id, int point){
        //親クラスのコンストラクタを呼び出す
        super(id, point);
        this.monthlyUse = 0;
    }

    //オーバーライドしたメソッド
    public void payment(int price) {
        //親クラスのpaymentメソッドを呼び出し
        super.payment(price);
        //利用回数を加算
        monthlyUse++;
        if( monthlyUse >= 2 ){
            //月2回以上の利用でボーナス
            point += 20;
        }
    }

    //現金からチャージするメソッド
    public void charge(int cash){
        point += cash;
    }
}

解説
修正箇所は14行目だけです。super.payment(price);の記述で親クラスのpaymentメソッドを呼び出しています。動作時のイメージは次のようになります。

  • メインクラスから、PrepaidCardインスタンスのpaymentメソッドが呼び出されると、オーバーライドしたpaymentメソッドが呼び出されます。
  • super.payment(price);で親クラスのpaymentメソッドを呼び出します。1000をそのまま引数として渡します。
  • 親クラス部分のpaymentメソッドが動作し、pointフィールドに1000の1%分のポイントを加算します。

このようにすることで、重複部分のプログラムを除外することができます。

12-6 親クラスと修飾子

親クラスに修飾子を適用すると、様々な作用をもたらします。本節ではこれらの様々な作用についてご紹介します。

12-6-1 アクセス修飾子

継承関係にあるクラスにおいても、アクセス修飾子の設定によっては、子クラスから親クラスのフィールドやメソッドにアクセスできなくなります。プログラム例を示します。

本節では説明を簡易化するため、親クラスにあたるParentクラスと子クラスにあたるChildクラスを使って説明します。

プログラム例(Parent.java)

public class Parent{
    private int data1;
}

プログラム例(Child.java)

public class Child extends Parent{
    public void process(){
        System.out.println(data1);
    }
}

この2つのプログラムをコンパイルすると、Child.javaで下のようなエラーとなります。

Child.java:3: エラー: data1はParentでprivateアクセスされます
        System.out.println(data1);
                           ^
エラー1個

Parentクラスにおいて、data1フィールドはprivateになっています。この場合、たとえ継承関係にある子クラスでも直接アクセスすることはできません。そのため、Child.javaの3行目におけるdata1の記述がエラーとなります。

下図のように、親クラスからの継承部分もひとつのカプセルの様になっていると考えると良いでしょう。

親クラスのprivate

12-6-2 オーバーライド禁止メソッド

親クラスのメソッド定義にfinalをつけると、オーバーライド禁止メソッドを作ることができます。プログラム例を示します。

プログラム例(Parent.java)

public class Parent{
    public final void process(){
        System.out.println("オーバーライド不可");
    }
}

プログラム例(Child.java)

public class Child extends Parent{
    public void process(){
        System.out.println("オーバーライドのテスト");
    }
}

この2つのプログラムをコンパイルすると、Child.javaで下のようなエラーとなります。

Child.java:2: エラー: Childのprocess()はParentのprocess()をオーバーライドできません
    public void process(){
                ^
  オーバーライドされたメソッドはfinalです
エラー1個

Parentクラスで定義されているChildのprocessメソッドには、final修飾子がつけられています。これによりChildのprocessメソッドはオーバーライド禁止メソッドとなります。

Childクラスではそのprocessメソッドをオーバーライドしようとしているため、エラーとなっています。

12-6-3 継承禁止クラス

クラス定義にfinalをつけると、継承禁止クラスを作成することができます。プログラム例を示します。

プログラム例(Parent.java)

public final class Parent{
}

プログラム例(Child.java)

public class Child extends Parent{
}

この2つのプログラムをコンパイルすると、Child.javaで下のようなエラーとなります。

Child.java:1: エラー: final Parentからは継承できません
public class Child extends Parent{
                           ^
エラー1個

定義部にfinal修飾子がつけられているParentクラスを、Childクラスで継承しようとしているためエラーとなっています。

タイトルとURLをコピーしました