継承したクラスのコンストラクタ

継承した場合には,複数のクラスが関連していることになる.そうすると,継承したクラスをインスタンス化するときには,コンストラクタはどのように作用しているだろうか.

Ramenクラスおよび,ChashuMenクラスのコンストラクタが実行されるタイミングを確認するために,RamenクラスのコンストラクタとChashuMenクラスのコンストラクタに,表示する命令を追加する.

public class Ramen
{
    protected int katasa;//1:固い,2:普通,3:柔い
    protected int men;//1-10
    protected Taste soup;
    Ramen()
    {
        System.out.println("ラーメンつくるよ");
    }
    public void setMen(int men)
    {
        this.men = men;
        System.out.println("量:" + men);
    }
    public void setKatasa(int katasa)
    {
        this.katasa = katasa;
        System.out.println("固さ:" + katasa);
    }
    public void setSoup(Taste taste)
    {
        this.soup = taste;
        System.out.println("スープ:" + this.soup);
    }
    public void checkRamen()
    {
        System.out.print("量:" + men);
        System.out.print("固さ:" + katasa);
        System.out.println("スープ:" + soup);
    }
}
public class ChashuMen extends Ramen
{
    private int chashu;
    ChashuMen()
    {
        System.out.println("チャーシューメン作るよ");
    }
    public void setChashu(int chashu)
    {
        this.chashu = chashu;
        System.out.println("チャーシュー:" + chashu);
    }
    public void checkChashuMen()
    {
        System.out.print("チャーシュー:" + chashu);
        if(soup != Taste.しょうゆ)
        {
            System.out.println("\nしょうゆ味でないチャーシューメンはチャーシューメンではない!");
            System.out.println("しょうゆ味に変えます!");
            soup != Taste.しょうゆ;
        }
    }
}

実行した結果は以下である. チャーシューメンを作る前に,ラーメンが作られていることがわかる.すなわち,ChashuMenクラスのコンストラクタが実行される前に,Ramenクラスのコンストラクタが実行されていることを意味している.

===ラーメン===
ラーメンつくるよ
量:5
固さ:2
スープ:しょうゆ
量:5固さ:2スープ:しょうゆ
===固めん,10盛り,みそ===
ラーメンつくるよ
固さ:1
量:10
スープ:みそ
量:10固さ:1スープ:みそ
===普通のチャーシューメン===
ラーメンつくるよ
チャーシューメン作るよ
量:5
固さ:2
チャーシュー:5
スープ:みそ
量:5固さ:2スープ:みそ
チャーシュー:5
しょうゆ味でないチャーシューメンはチャーシューメンではない!
しょうゆ味に変えます!

これは,スタッククラスをインスタンス化するときには,スタッククラスのコンストラクタを実行する前に,スーパークラスのコンストラクタを実行していることが分かる.すなわち,スーパークラスをインスタンス化してから,継承元のクラスをインスタンス化している.スーパークラスを先に明示的に実行するための表現として,以下のように表わすことができる.

ChashuMen()
{
    super();
    System.out.println("チャーシューメン作るよ");
}

superは,スーパークラスを表わすためのキーワードである.「()」を付けると,メソッドを表わすので,super()は,スーパークラス名に()を付けることになるので,スーパークラスのコンストラクタを意味する.super()を書かないと,暗示的に,super()が実行されていたのである. 

ここで,注意として,サブクラスがスーパークラスのコンストラクタを呼び出すときには,コンストラクタの最初に位置するように記述しなければならない.たとえば,スーパークラスのコンストラクタを実行する前に,インスタンス化することを説明する表示すると,以下のように,エラーが表示される.


これは,Ramenクラスのコンストラクタを実行する前に,ChashuMenクラスのコンストラクタでの処理を開始していることを意味している.これは,継承の関係から,不適切な順番であることの現れである.

すなわち,ChashuMenクラスは,Ramenクラスを継承しているので,Ramenクラスが確定しないと,ChashuMenクラスの処理を始めることができない.しかし,super()にてスーパークラスをインスタンス化する前に処理を始めてしまっているのである.

継承におけるコンストラクタのオーバーロード

継承したとき,スーパークラスとサブクラスでのコンストラクタにおいて, オーバーロードされている場合はどのようになるだろうか.

まず,スーパークラスで引数をもたないコンストラクタと,引数を持つコンストラクタが定義されている場合を考える. Ramenクラスをインスタンス化して,後から麺のかたさ,麺の量,スープの味を指定するのではなく,インスタンス化するときに一緒に麺のかたさ,麺の量,スープの味を指定することもできるようにしたい.

このときのコンストラクタを含むコードは以下のようになる.

enum Taste
{
    しょうゆ,みそ
}
public class Ramen
{
    protected int katasa;//1:固い,2:普通,3:柔い
    protected int men;//1-10
    protected Taste soup;
    Ramen()
    {
        System.out.println("ラーメンつくるよ");
    }
    Ramen(int men, int katasa, Taste taste)
    {
        System.out.println("指定のラーメンつくるよ");
        setMen(men);
        setKatasa(katasa);
        setSoup(taste);
    }
    public void setMen(int men)
    {
        this.men = men;
        System.out.println("量:" + men);
    }
    public void setKatasa(int katasa)
    {
        this.katasa = katasa;
        System.out.println("固さ:" + katasa);
    }
    public void setSoup(Taste taste)
    {
        this.soup = taste;
        System.out.println("スープ:" + this.soup);
    }
    public void checkRamen()
    {
        System.out.print("量:" + men);
        System.out.print("固さ:" + katasa);
        System.out.println("スープ:" + soup);
    }
}

このラーメンをインスタンス化するには以下のようにする.比較のために,後から麺の硬さや量,スープの味を指定する場合も併記している.

public class Main
{
    public static void main(String[] args)
    {
        //ラーメンくれ
        System.out.println("===ラーメン===");
        Ramen ramen1 = new Ramen();
        ramen1.setMen(5);
        ramen1.setKatasa(2);
        ramen1.setSoup(Taste.しょうゆ);
        ramen1.checkRamen();
        System.out.println("===指定のラーメン===");
        Ramen RamenNormal = new Ramen(5,2,Taste.みそ);
        RamenNormal.checkRamen();
    }
}

この時の出力結果は以下のようになる.

===ラーメン===
ラーメンつくるよ
量:5
固さ:2
スープ:しょうゆ
量:5固さ:2スープ:しょうゆ
===指定のラーメン===
指定のラーメンつくるよ
量:5
固さ:1
スープ:みそ
量:5固さ:1スープ:みそ

指定のラーメンをつくるときと同様に,指定のチャーシューメンを作ることを考える. インスタンス化するときに,麺の硬さと量,スープの味を指定する. 以下のように,先で引数を持つコンストラクタ定義したRamenクラスを継承するChashuMenクラスにて, 引数を持つコンストラクタを定義する.

public class ChashuMen extends Ramen
{
    private int chashu;
    ChashuMen()
    {
        System.out.println("チャーシューメン作るよ");
    }
    ChashuMen(int men, int katasa, Taste taste)
    {
        System.out.println("指定済みのチャーシューメン作るよ");
    }
    public void setChashu(int chashu)
    {
        this.chashu = chashu;
        System.out.println("チャーシュー:" + chashu);
    }
    public void checkChashuMen()
    {
        System.out.print("チャーシュー:" + chashu);
        if(soup != Taste.しょうゆ)
        {
            System.out.println("\nしょうゆ味でないチャーシューメンはチャーシューメンではない!");
            System.out.println("しょうゆ味に変えます!");
            soup = Taste.しょうゆ;
        }
    }
}

引数を持つChashuMenのコンストラクタを実行してインスタンス化してみる.

public class Main
{
    public static void main(String[] args)
    {
        System.out.println("===普通のチャーシューメン===");
        ChashuMen chashuMenNormal = new ChashuMen(2,5,Taste.みそ);
        chashuMenNormal.setChashu(5);
        chashuMenNormal.checkRamen();
    }
}

この時の実行結果は以下のようになる.

===指定のチャーシューメン===
ラーメンつくるよ
指定済みのチャーシューメン作るよ
チャーシュー:5
量:0固さ:0スープ:null
チャーシュー:5
しょうゆ味でないチャーシューメンはチャーシューメンではない!
しょうゆ味に変えます!

この結果になるのはなぜだろうか.それは,自動的に呼ばれるスーパークラスのコンストラクタを明示してみるとわかる.明示すると以下のようになっている.すなわち,サブクラスのコンストラクタを呼ぶ前に,スーパークラスのコンストラクタを呼ばれるが,明示しない場合は,引数なしのコンストラクタが呼ばれる.サブクラスで引数を持つコンストラクタをオーバーロードしても,そのスーパークラスのコンストラクタ呼び出しの際には,引数は与えられない.そこで,スーパークラスにふくまれる,引数を持つコンストラクタを指定したい時には,明示的にスーパークラスのコンストラクタを呼び出さなければならない.

    ChashuMen()
    {
        super();
        System.out.println("チャーシューメン作るよ");
    }
    ChashuMen(int men, int katasa, Taste taste)
    {
        super();
        System.out.println("指定済みのチャーシューメン作るよ");
    }
}

すなわち,以下のように指定する.

    ChashuMen(int men, int katasa, Taste taste)
    {
        super(men,katasa,taste);
        System.out.println("指定済みのチャーシューメン作るよ");
    }

このようにすると,サブクラスをインスタンス化するまえに呼び出される,スーパークラスのコンストラクタを実行するときに,引数を持つコンストラクタを明示して呼び出すことができる.ChashuMenクラスを引数を用いたコンストラクタを指定して呼び出すと,以下のようになる.

===指定のチャーシューメン===
指定のラーメンつくるよ
量:2
固さ:5
スープ:みそ指定済みのチャーシューメン作るよ
チャーシュー:5
量:2固さ:5スープ:みそ
チャーシュー:5
しょうゆ味でないチャーシューメンはチャーシューメンではない!
しょうゆ味に変えます!

指定のチャーシューメンを作るために,指定のラーメンが作られている,すなわち,引数をもつコンストラクタが明示的に呼び出されていることが分かる.

演習

タンメンクラスのコンストラクタとして,メンの量,硬さ,スープ,野菜の量をまとめて指定するコンストラクタを設定せよ.設定後には,それぞれの項目が設定されていることを確認せよ,

継承したクラスのコンストラクタ