継承とオーバーライド

ここからはオブジェクト指向の具体的なプログラミングについて学んでいきます。

継承とは

継承(inheritance)とは既存のクラスをもとに新しい別のクラスを作成し、既存のクラスの機能を受け継がせることをいいます。
このとき、継承元のことを親クラス(スーパークラス)継承先のことを子クラス(サブクラス)と呼びます。

例えば「ノートパソコン」を作成したいとします。
ノートパソコンはコンピュータの一種なので、「コンピュータ」と「ノートパソコン」には多くの共通点がみえてきます。
そのような場合、Javaでは既存の「Computerクラス」の機能を、ノートパソコンを表す「NotePcクラス」に継承させることができます。

つまりここでは、コンピュータが「親クラス」、ノートパソコンが「子クラス」となります。

継承を用いたプログラミング

以下のプログラミングで実際に継承について見ていきましょう。
「com.cmps.inheritance」パッケージを作成し、
その中に「Employee」「Normal」「Management」「Inheritance」クラスを作成してください。

・Employeeクラス

public class Employee {
    protected String name;
    protected int age;
    protected int saraly;
    
    public Employee() {
        System.out.println("サラリーマンを起動");
        this.name = "一般一郎";
        this.age = 25;
        this.saraly = 1000;
    }
    
    public void announce() {
        System.out.println("名前は" + name + "です。");
        System.out.println("社員です");
        System.out.println("給料は" + saraly +"円です。");
        System.out.println("年齢は" + age + "です。");
    }
}

・Normalクラス

public class Normal extends Employee{
    public Normal() {
        super();
    }
}

・Managementクラス

public class Management extends Employee{
    public Management() {
        super();
        System.out.println("管理職を起動します。");
        this.name = "菅理二郎";
        this.saraly = 3000;
        this.age = 30;
    }
}

・Inheritanceクラス
 mainメソッドはこのクラスに作成します。

public class Inheritance {

    public static void main(String[] args) {
        // インスタンス化
        Normal normal = new Normal();
        // Nomalに処理が無くても、Employeeの値を継承して処理できる
        normal.announce();
        
        System.out.println();

        //インスタンス化
        //親クラスのコンストラクタが起動してから子クラスの管理職が動き出す。
        Management management = new Management();
        management.announce();
        
        System.out.println();
        
    }
}

実行すると以下の様な出力結果になると思います。

サラリーマンを起動
名前は一般ー郎です。
社員です
給料は1000円です。
年齢は25歳です。

サラリーマンを起動
管理職を起動します。
名前は菅理二郎です。
社員です
給料は3000円です。
年齢は30歳です。

各クラスを見てみましょう。

Employeeクラス

コンストラクタ(Employee)が一つとメソッド(announce)が一つあります。
このEmployeeクラスは継承元である「親クラス」となります。

親クラスとなるEmployeeクラスのフィールド変数には、
アクセス修飾子「protected 」がついており、子クラスからのアクセスが可能となっています。

Normalクラス

Normalクラスの中には、デフォルトのコンストラクタしか書かれていないため、このままでは何もできません。
しかし、クラスの指定を見てみると「public class Normal extends Employee」と記載されています。

この「extends」は「拡張」と呼ばれ、他のクラスを継承したクラスであると指定することができます。
この処理では「NormalクラスはEmployee分類の中にあるNormalです。」ということになります。

拡張をするメリットですが、共通する機能の部分を親クラス(スーパークラス)として作り、
各クラス固有の機能をもった子クラス(サブクラス)で拡張し、親クラスの機能を再利用することで、プログラム効率が高まります。

拡張のイメージ図

上図が示すように、NormalクラスはEmployeeクラスを受け継ぎます。
このため、Employeeクラスにある機能(メンバ)は作成する必要がありません。Normalクラス独自の機能(メンバ)だけ作成すればよいことになるのです。

super()

Nomalクラスのコンストラクタには「super()」が記述されています。
これは、1世代上の親クラスのコンストラクタを指します。

この「super()」は書いても書かなくても挙動自体は一緒です。
というのも、「super()」は実はすべてのクラスのコンストラクタ内に暗黙的に含まれているのです(デフォルトコンストラクタが無い場合、コンパイルエラーとなります)。
書く場合は、コンストラクタの1行目に記述する決まりになっています。

今回、NormalクラスはInheritanceクラスでインスタンス化をすると
Employeeクラス内のコンストラクタが呼び出され、そのまま実行されます。

継承の書式

クラスの拡張する方法ですが、基本構文としては以下の通りになります。

// クラスの拡張
class 子クラス(サブクラス)名 extends 親クラス(スーパークラス)名
{
 子クラスに追加するメンバ(機能)
 子クラスのコンストラクタ
}

新しいクラス名の横に「extends 継承元のクラス名」と記述すれば、
これだけで子クラスを拡張し親クラスの機能全てを受け継ぐこと(継承)が可能になります。
後は親クラスにはない、子クラスに必要な機能(メンバ)を記述すればよいです。

Managementクラス

ManagementクラスもNormalクラスと同じく「public class President extends Employee」と記載があり、
Employeeクラスを継承していることがわかります。

Normalクラスと違う点は、「public Management()」コンストラクタが存在する点です。
この状態で処理を実行すると、以下の様な出力結果になっていると思います。

サラリーマンを起動
管理職を起動します。

Javaでは全てのコンストラクタは、その先頭で必ず内部インスタンス部分(親クラス)のコンストラクタを呼び出さなければならない、
というルールになっているため、管理職のコンストラクタだけでなく親クラスのコンストラクタも起動しています。

これはインスタンスの内部にインスタンスを持っているために起こります。

継承クラスのインスタンスは複数のインスタンスで構成されています。
今回の処理だと、Employeeインスタンスが先に作られた後、そのインスタンスを囲うようにしてManagementインスタンスを作ります。
先にEmployeeのインスタンスも作ることになるため、コンストラクタが自動的に作成されるんですね。

オーバーライド

オーバーライドとは、親クラス(スーパークラス)で定義されたインスタンスメソッドを、
そのクラスを継承した子クラス(サブクラス)で独自に定義し直して上書きすること
をいいます。
子クラスで機能を追加する必要がある場合などにオーバーライドを利用します。
なお、オーバーライドを利用するには、メソッド名、戻り値の型、引数の型と数及び並びが完全に同じでなければなりません。

オーバーライドを使用するメリットとしては、
・親クラスから継承したメソッドの代わりに、子クラスのメソッドを動作させることができる。
・1つのメソッド名でそのオブジェクトのクラスに応じて、適切な処理(メソッド)を行わせることができる。
・複数のメソッド名を定義したり覚えたりする必要がなくなる。
といった点が挙げられます。

これらのメリットを見ても、あまり便利とは思えないかもしれません。
オーバーライドを行うのは「継承した親クラスのメソッドでは実現したい機能が満たせない、
でも利用するメソッド名を変更することができない」といった場合になります。

別の名前で似たような処理を行うメソッドを作成すると管理が大変になるため、
このオーバーライドという機能が提供されています。

「com.cmps.inheritance」パッケージ内に、「President」クラスを作成してください。
・Presidentクラス

public class President extends Employee {
    public President() {
        super();
        System.out.println("社長を起動します");
        this.name = "社長三郎";
        this.saraly = 5000;
        this.age = 35;
    }

    @Override
    public void announce() {
        System.out.println("名前は" + name + "です。");
        System.out.println("この会社の社長です");
        System.out.println("給料はもらっていません。");
        System.out.println("年齢は" + age + "歳です。");
    }
}

続いて、Inheritanceクラスに以下のソースを追記してください
・Inheritanceクラス

        //インスタンス化
        President president = new President();
        //上書きされたアナウンスが出力される
        president.announce();

Presidentクラス

Managementクラスと比べると「@Override(アノテーション)」がある点が違いとしてありますね。
アノテーションは、オーバーライドすることを宣言するために記述しています。
これを記述することで、もし親クラスに同名のメソッドがなければコンパイラがエラーメッセージを出しますので、付けておくことをオススメします。
なお、記述しなくても問題はありません。

アノテーションについて詳しくは以下のサイトを参照してください。
参照サイト:【Java入門】アノテーションの使い方と作成する方法

この状態でインスタンス化をすると、以下の様に実行されるかと思います。

サラリーマンを起動
社長を起動します
名前は社長三郎です。
この会社の社長です
給料はもらっていません。
年齢は35歳です。

Employeeクラスのコンストラクタが動いてから、Presidentクラスのコンストラクタが起動しています。
ここまではManagementクラスと同様です。

しかし、announce()メソッドを実行するとEmployeeクラスに記載していた内容ではなく
Presidentクラスに記載されている処理が実行されています。

今回のPresidentクラスの処理では、announce()というメソッド処理に対して@Overrideを使って処理を上書きし、
Employeeクラスの代わりにPresidentクラスのannounce()メソッドを実行させるようにされています。
共通の処理だけど微妙に違う処理を作ることができる、多態化の実現ができていますね。

オーバーライドとオーバーロード
似たような用語でオーバーロードがあります。
オーバーロードとは同じクラス内で同じメソッド名で、引数の数や順番が違うメソッドを定義することです。
今回解説しているオーバーライドは継承した子クラスで同じメソッド名で、引数の数や順番が同じメソッドを再定義することです。

継承元のクラスを使ってインスタンス作成

継承されたクラスはデータ型の指定をする時に、親クラスを指定して、
同じデータ型として扱うことができる、という利点があります。

インスタンス化した後は各子クラスの動きと同じように振る舞いますが、
全て同じデータ型として扱うことができる為、同じデータ型だけど似た別の動きを行う、といったことが可能になります。

Inheritanceクラスに以下のソースを追記位しましょう。
・Inheritanceクラス

    // 継承元のクラスを使ってインスタンスを作成することことも出来る
        // Normalクラス、Managementクラス、Presidentクラスもざっくり同じEmployeeとして使える
        // int aを1~3の数字を入れて処理を見る
        System.out.println("継承元の処理からインスタンス作成も可能");
        int num = 1;
        Employee sarary;
        switch (num) {
            case 1: 
                sarary = new Normal();
                sarary.announce();
                break;
            case 2: 
                sarary = new Management();
                sarary.announce();
                break;
            case 3:
                sarary = new President();
                sarary.announce();
                break;
            }

numに1~3のいずれかを格納することで、
「Normal」「Management」「President」のそれぞれの処理が出力されたことが確認できたかと思います。

サブクラスからスーパークラスへのアクセス(superメソッド)

superメソッドはサブクラス(子クラス)のインスタンスから、
スーパークラス(親クラス)のインスタンスにアクセスするための修飾子です。

superを使うことにより、親クラスのフィールドやメソッドを、サブクラスから参照できるようになります。

実際に試してみましょう。
「Employee」と「Management」に追記していきます。

・Employee

public class Employee {
    protected String name;
    protected int age;
    protected int saraly;

    protected int years; // 追記 勤続年数(親クラス)
    
    public Employee() {
        System.out.println("サラリーマンを起動");
        this.name = "一般一郎";
        this.age = 25;
        this.saraly = 1000;
        
        this.years = 3;  // 追記
    }
    
    public void announce() {
        System.out.println("名前は" + name + "です。");
        System.out.println("社員です");
        System.out.println("給料は" + saraly +"円です。");
        System.out.println("年齢は" + age + "歳です。");
        
        System.out.println("勤続年数は"+ years +"年です。"); // 追記
    }
}

・Management

public class Management extends Employee{
    
    int years; // 追記 勤続年数(子クラス)
    
    public Management() {
        System.out.println("管理職を起動します。");
        this.name = "菅理二郎";
        this.saraly = 3000;
        this.age = 30;
       
        this.years = 5; // 追記 thisを使って子クラスのyearsを設定
        super.years = 10; // 追記 superを使って親クラスのyearsを変更
    }
    
    // 追記
    @Override
    public void announce() {
        System.out.println("名前は" + name + "です。");
        System.out.println("社員です");
        System.out.println("給料は" + saraly +"円です。");
        System.out.println("年齢は" + age + "歳です。");
        System.out.println("(子クラス) 勤続年数は " + this.years + "年です。");
        System.out.println("(親クラス) 勤続年数は " + super.years + "年です。");
    }
}

実行してみると、「Employee」クラスの勤続年数は「3年」ですが、
「Management」クラスの勤続年数は「5年」と「10年」がそれぞれ出力されたかと思います。

this.years」がManagementで宣言された「years」を指すのに対し
super.years」はEmployeeで宣言された「years」を指しています。

このように「super」を利用することで、親クラスのフィールドへアクセス、
オーバーライドして変更等が可能となります。

ちなみにこのsuperメソッドですが、つけなくてもアクセスすることは可能です。
ただし、子クラスのローカル変数やメンバの名前に同じものがある場合、「super」をつける必要があります。
これには名前の優先度が関係しており

子クラスのローカル変数 > サブクラスのメンバ名 > 親クラスのメンバ名

この優先度を無視してメンバへアクセスしたい場合、「this」や「super」をつけるようにしましょう。

練習問題

以下のルールに従って、複数の動物クラスを作成しそれぞれの鳴き声を出力するプログラムを記述しましょう。
「com.cmps.inheritance」パッケージ内に「inheritanceQuestion」パッケージを作成し、各クラスを配置しましょう。

実行クラスとして「com.cmps.inheritance.inheritanceQuestion」内に
「inheritanceQuestion」クラスを作成し、mainメソッドもこのクラスへ記述してください。

ルール
1.Animal クラスを継承した Dog, Catクラスを作成

2.Animalクラスにspeakメソッドを作成、メソッド内に以下を記述

System.out.println("誰かが鳴いています。");

3.各クラスでspeakメソッドをオーバーライドし、以下のように出力できるようにすること

誰かが鳴いています。
にゃーにゃー

誰かが鳴いています。
わんわん

4.main メソッドでは List に Dog, Catを追加し、拡張for文で全ての動物に speak() を実行させる

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