Java超|親クラスと子クラスのコンストラクタの関係

子クラスが完成する前に、まず親クラスの土台が作られる。コンストラクタの呼び出し順を理解すると、継承したオブジェクトがどのように完成していくのかが立体的に見えてきます。

Javaで継承を使うと、子クラスは親クラスのフィールドやメソッドを受け継ぐことができます。

前回までの内容では、SuperSaiyanWarrior が SaiyanWarrior を継承すると、親クラス側にある setWarrior() や show() を、子クラスのオブジェクトから呼び出せることを確認しました。

ここで次に大切になるのが、子クラスのオブジェクトを作ったとき、コンストラクタはどの順番で動くのかという点です。

サブクラスのオブジェクトも、通常どおり new を使って作成します。

たとえば、次のように書きます。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior();

見た目だけを見ると、SuperSaiyanWarrior のオブジェクトだけを作っているように見えます。

しかし、Javaの内部では、子クラスだけがいきなり完成するわけではありません。

SuperSaiyanWarrior は SaiyanWarrior を継承しているため、まず親クラスである SaiyanWarrior の部分が作られます。
そのあとで、子クラスである SuperSaiyanWarrior の追加部分が作られます。

ドラゴンボールの世界観でたとえると、スーパーサイヤ人戦士が登場するとき、いきなりスーパーサイヤ人としてだけ完成するわけではありません。

まず、サイヤ人戦士としての基本登録が行われます。

名前や戦闘力を持つための土台が整います。
そのあとで、スーパーサイヤ人としての変身段階が追加されます。

つまり、親の土台が先にできて、その上に子の個性が乗るという流れです。

これが、親クラスと子クラスのコンストラクタの基本的な関係です。この記事では、Sample1.java と Sample2.java を使いながら、サブクラスのオブジェクト作成、親子コンストラクタの順番、super() の働き、this() との違いを、ドラゴンボールのサイヤ人クラス設計として整理していきます。

サブクラスのオブジェクトは new で作る

サブクラスのオブジェクト作成は、特別に難しいものではありません。

基本はこれまでと同じように、new を使います。

たとえば、SaiyanWarrior を継承した SuperSaiyanWarrior がある場合、次のように書きます。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior();

この1行で、SuperSaiyanWarrior 型のオブジェクトが作成されます。

ただし、この warrior1 は、SuperSaiyanWarrior だけの機能を持つわけではありません。

SaiyanWarrior から受け継いだ機能も使えます。

ドラゴンボール風にたとえると、スーパーサイヤ人戦士は、変身段階という特別な特徴を持っています。

しかし、同時にサイヤ人戦士でもあります。

そのため、サイヤ人戦士としての名前や戦闘力を持ち、さらにスーパーサイヤ人としての変身段階も持てるわけです。

見るポイント内容
作成するクラスSuperSaiyanWarrior
継承元SaiyanWarrior
使える親の機能setWarrior()、show()
使える子の機能setTransformationLevel()

このように、サブクラスのオブジェクトは、親から受け継いだ機能と、子で追加した機能の両方を使えます。

継承したメソッドと追加したメソッドはどちらも使える

サブクラスのオブジェクトでは、親クラスから受け継いだメソッドと、子クラスで追加したメソッドの両方を呼び出せます。

今回の例では、SuperSaiyanWarrior オブジェクトから次のメソッドを使えます。

メソッド定義されている場所役割
setWarrior()SaiyanWarrior名前と戦闘力を設定する
show()SaiyanWarrior戦士の状態を表示する
setTransformationLevel()SuperSaiyanWarrior変身段階を設定する

ここで大切なのは、SuperSaiyanWarrior の中に setWarrior() や show() を書いていなくても使えることです。

これは、SuperSaiyanWarrior が SaiyanWarrior を継承しているからです。

継承とは、親クラスの内容を子クラスに同じように書き直すことではありません。

親クラスの機能を受け継ぎ、必要な追加機能だけを子クラスに書く仕組みです。

ドラゴンボール風にたとえると、スーパーサイヤ人戦士は、サイヤ人戦士としての基本訓練を最初から受け継いでいます。

そこに、変身段階という追加能力を持たせているイメージです。

図:親クラスの土台が先に作られる

この図が示していること

この図では、new SuperSaiyanWarrior() を実行したとき、まず SaiyanWarrior() が動き、そのあと SuperSaiyanWarrior() が動く流れを表しています。

最初に親クラス側で name と battlePower が初期化されます。

そのあと、子クラス側で transformationLevel が初期化されます。

ここから分かるのは、サブクラスのオブジェクトは、親クラスの土台が作られてから、子クラスの追加部分が作られるということです。

スーパーサイヤ人戦士は、まずサイヤ人戦士としての基本部分を持ち、その上に変身段階という追加情報を持つ形で完成します。

クラスの継承を確認する

ファイル名:Sample1.java

class SaiyanWarrior
{
    private String name;
    private int battlePower;

    public SaiyanWarrior()
    {
        name = "戦士未登録";
        battlePower = 0;
        System.out.println("サイヤ人戦士クラスの戦士を作成しました。");
    }

    public void setWarrior(String n, int b)
    {
        name = n;
        battlePower = b;
        System.out.println("名前を" + name + "に、戦闘力を" + battlePower + "にしました。");
    }

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

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 + "にしました。");
    }
}

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

        warrior1.setWarrior("悟空", 9000);
        warrior1.setTransformationLevel(1);

        warrior1.show();
    }
}

このプログラムでは、SaiyanWarrior がスーパークラス、SuperSaiyanWarrior がサブクラスです。

SaiyanWarrior には、名前と戦闘力を管理する共通機能があります。

SuperSaiyanWarrior には、変身段階を管理する追加機能があります。

クラス役割
SaiyanWarriorサイヤ人戦士としての基本情報を持つ
SuperSaiyanWarriorスーパーサイヤ人としての変身段階を追加する
Sample1オブジェクトを作って、親と子の機能を呼び出す

main メソッドでは、次の行で SuperSaiyanWarrior オブジェクトを作っています。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior();

このとき、見た目は SuperSaiyanWarrior のオブジェクトだけを作っているように見えます。

しかし、SuperSaiyanWarrior は SaiyanWarrior を継承しているため、最初に SaiyanWarrior 側のコンストラクタが動きます。

そのあとで、SuperSaiyanWarrior 側のコンストラクタが動きます。

Sample1.java の処理の流れ

Sample1.java の処理の流れを整理すると、次のようになります。

順番処理内容
1SaiyanWarrior()name と battlePower を初期化する
2SuperSaiyanWarrior()transformationLevel を初期化する
3setWarrior("悟空", 9000)名前と戦闘力を設定する
4setTransformationLevel(1)変身段階を設定する
5show()名前と戦闘力を表示する

ドラゴンボール風にたとえると、まずサイヤ人戦士として登録され、そのあとスーパーサイヤ人として変身段階を持つ流れです。

ここで注目したいのは、SuperSaiyanWarrior のオブジェクトなのに、SaiyanWarrior() が先に動くことです。

これは、子クラスが親クラスの土台の上に成り立っているからです。

実行結果の例

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

最初に表示されているのは、SaiyanWarrior のコンストラクタのメッセージです。

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

次に、SuperSaiyanWarrior のコンストラクタのメッセージが表示されます。

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

この順番がとても重要です。

子クラスのオブジェクトを作っているのに、先に親クラスのコンストラクタが動いています。

これは、子クラスの中には親クラスから受け継いだ部分があり、その土台を先に初期化する必要があるからです。

コンストラクタは継承されない

継承では、親クラスのフィールドやメソッドを子クラスが利用できます。

しかし、コンストラクタは継承されません。

ここは少し混ざりやすいので、表で整理します。

項目継承されるか
フィールド継承の対象になる
メソッド継承の対象になる
コンストラクタ継承されない

ただし、コンストラクタが継承されないからといって、親クラスのコンストラクタが無関係になるわけではありません。

サブクラスのオブジェクトを作るとき、親クラスのコンストラクタは呼び出されます。

つまり、コンストラクタは子クラスに受け継がれるのではなく、子クラスの生成時に親クラス側の初期化として呼び出される、ということです。

ドラゴンボール風にたとえると、スーパーサイヤ人戦士は、サイヤ人戦士としての登録手順そのものを受け継ぐわけではありません。

しかし、スーパーサイヤ人として完成する前に、必ずサイヤ人戦士としての基本登録が行われる、というイメージです。

何も書かないと super() が自動的に呼ばれる

SuperSaiyanWarrior のコンストラクタを見てみましょう。

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

この中には super() が書かれていません。

しかし、Javaではサブクラスのコンストラクタの先頭に何も書かない場合、自動的に親クラスの引数なしコンストラクタが呼ばれます。

つまり、内部的には次のようなイメージです。

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

この super() によって、SaiyanWarrior() が先に動きます。

書いたコードJava内部のイメージ
super() を書いていない自動的に super() が呼ばれる
super(n, b) を書く指定した親コンストラクタを呼ぶ

ドラゴンボール風にたとえると、スーパーサイヤ人として登録する前に、まずサイヤ人戦士としての登録処理が自動的に呼ばれるようなものです。

図:new SuperSaiyanWarrior() のコンストラクタ順序

この図が示していること

この図では、new SuperSaiyanWarrior() を実行したときのコンストラクタの順番を表しています。

最初に SaiyanWarrior() が動きます。

ここで、サイヤ人戦士としての基本情報である name と battlePower が初期化されます。

そのあとに SuperSaiyanWarrior() が動きます。

ここで、スーパーサイヤ人としての追加情報である transformationLevel が初期化されます。

このように、サブクラスのオブジェクトは、親クラスの部分、子クラスの部分という順番で完成します。

super() で親クラスのコンストラクタを指定する

親クラスにコンストラクタが複数ある場合、子クラス側からどの親コンストラクタを呼ぶか指定できます。

そのときに使うのが super() です。

たとえば、SaiyanWarrior に次の2つのコンストラクタがあるとします。

コンストラクタ内容
SaiyanWarrior()初期値でサイヤ人戦士を作る
SaiyanWarrior(String n, int b)名前と戦闘力を受け取ってサイヤ人戦士を作る

SuperSaiyanWarrior 側で super(n, b) と書くと、SaiyanWarrior(String n, int b) を呼び出せます。

これにより、子クラスのオブジェクト作成時に、親クラス側の名前と戦闘力もまとめて初期化できます。

ドラゴンボール風にたとえると、スーパーサイヤ人戦士を作るときに、まずサイヤ人戦士としての名前と戦闘力を登録し、そのあとで変身段階を登録する流れです。

親クラスのコンストラクタを確認する

ファイル名:Sample2.java

class SaiyanWarrior
{
    private String name;
    private int battlePower;

    public SaiyanWarrior()
    {
        name = "戦士未登録";
        battlePower = 0;
        System.out.println("サイヤ人戦士クラスの戦士を作成しました。");
    }

    public SaiyanWarrior(String n, int b)
    {
        name = n;
        battlePower = b;
        System.out.println("名前が" + name + "で、戦闘力が" + battlePower + "のサイヤ人戦士を作成しました。");
    }

    public void setWarrior(String n, int b)
    {
        name = n;
        battlePower = b;
        System.out.println("名前を" + name + "に、戦闘力を" + battlePower + "にしました。");
    }

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

class SuperSaiyanWarrior extends SaiyanWarrior
{
    private int transformationLevel;

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

    public SuperSaiyanWarrior(String n, int b, int level)
    {
        super(n, b);
        transformationLevel = level;
        System.out.println("変身段階が" + transformationLevel + "のスーパーサイヤ人クラスの戦士を作成しました。");
    }

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

class Sample2
{
    public static void main(String[] args)
    {
        SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior("悟空", 9000, 1);

        warrior1.show();
    }
}

Sample2.java では、次の行で SuperSaiyanWarrior オブジェクトを作っています。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior("悟空", 9000, 1);

このとき、SuperSaiyanWarrior の引数付きコンストラクタが呼ばれます。

public SuperSaiyanWarrior(String n, int b, int level)
{
    super(n, b);
    transformationLevel = level;
    System.out.println("変身段階が" + transformationLevel + "のスーパーサイヤ人クラスの戦士を作成しました。");
}

最初に書かれているのが、super(n, b) です。

この super(n, b) は、親クラス SaiyanWarrior の引数付きコンストラクタを呼び出しています。

public SaiyanWarrior(String n, int b)
{
    name = n;
    battlePower = b;
    System.out.println("名前が" + name + "で、戦闘力が" + battlePower + "のサイヤ人戦士を作成しました。");
}

処理の順番を整理すると、次のようになります。

順番処理内容
1super(n, b)親クラス側で名前と戦闘力を初期化する
2transformationLevel = level子クラス側で変身段階を初期化する
3メッセージ表示スーパーサイヤ人クラスの戦士として作成完了を表示する
4show()親クラスのメソッドで名前と戦闘力を表示する

ドラゴンボール風にたとえると、悟空というサイヤ人戦士が戦闘力9000で登録され、そのあと変身段階1を持つスーパーサイヤ人戦士として完成する流れです。

Sample2.java の実行結果の例

名前が悟空で、戦闘力が9000のサイヤ人戦士を作成しました。
変身段階が1のスーパーサイヤ人クラスの戦士を作成しました。
戦士の名前は悟空です。
戦闘力は9000です。

この結果を見ると、まず SaiyanWarrior 側のコンストラクタが動いています。

名前が悟空で、戦闘力が9000のサイヤ人戦士を作成しました。

そのあと、SuperSaiyanWarrior 側のコンストラクタが動いています。

変身段階が1のスーパーサイヤ人クラスの戦士を作成しました。

ここから、super(n, b) によって親クラスの引数付きコンストラクタが呼び出され、そのあとで子クラスの追加処理が行われていることが分かります。

super() を使うと何が便利なのか

super() を使うと、親クラス側の初期化をオブジェクト作成時にまとめて行えます。

Sample1.java では、いったん引数なしで SuperSaiyanWarrior を作ってから、あとで setWarrior() を使って名前と戦闘力を設定しました。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior();

warrior1.setWarrior("悟空", 9000);

一方、Sample2.java では、オブジェクトを作る時点で名前、戦闘力、変身段階を渡しています。

SuperSaiyanWarrior warrior1 = new SuperSaiyanWarrior("悟空", 9000, 1);

この違いを表にすると、次のようになります。

書き方特徴
new SuperSaiyanWarrior() のあとで setWarrior()作成後に名前と戦闘力を設定する
new SuperSaiyanWarrior("悟空", 9000, 1)作成時に名前、戦闘力、変身段階を設定する

必須情報を最初から持たせたい場合は、引数付きコンストラクタと super() を使うと自然です。

ドラゴンボール風にたとえると、Sample1.java は、まず戦士を登録してから、あとで名前と戦闘力を本部台帳に書き込む流れです。

Sample2.java は、登場時点で名前、戦闘力、変身段階までまとめて登録する流れです。

super() はコンストラクタの先頭に書く

super() には重要なルールがあります。

それは、コンストラクタの先頭に書く必要があるということです。

正しい書き方は次の形です。

public SuperSaiyanWarrior(String n, int b, int level)
{
    super(n, b);
    transformationLevel = level;
}

次のように、先に子クラス側の処理を書いてから super() を呼ぶことはできません。

public SuperSaiyanWarrior(String n, int b, int level)
{
    transformationLevel = level;
    super(n, b);
}

これは、親クラスの初期化が終わる前に子クラス側の処理を始めることを防ぐためです。

ドラゴンボール風にたとえると、スーパーサイヤ人としての変身段階を登録する前に、まずサイヤ人戦士としての基本登録を済ませる必要がある、ということです。

親の土台がまだできていない状態で、子の追加情報だけを先に完成させることはできません。

図:super() で親コンストラクタを呼ぶ流れ

この図が示していること

この図では、SuperSaiyanWarrior の引数付きコンストラクタから super(n, b) を使って、SaiyanWarrior の引数付きコンストラクタを呼び出す流れを表しています。

まず、SaiyanWarrior(String n, int b) で name と battlePower を初期化します。

そのあと、SuperSaiyanWarrior 側で transformationLevel を初期化します。

つまり super() は、親クラスのコンストラクタを呼び、スーパークラス側の初期化を行うための仕組みです。

また、引数を使うことで、呼び出す親コンストラクタを指定できます。

親を先に、子をあとに初期化するという順番を守るためにも、super() はコンストラクタの先頭に書く必要があります。

this() と super() の違い

コンストラクタの中では、this() という書き方も出てきます。

this() と super() は似ていますが、呼び出す相手が違います。

記述呼び出すもの
this()同じクラスの別のコンストラクタ
super()スーパークラスのコンストラクタ

ドラゴンボール風にたとえると、this() は同じ SuperSaiyanWarrior クラス内の別の登録手順を使うことです。

super() は、親である SaiyanWarrior クラスの登録手順を使うことです。

書き方ドラゴンボール風のイメージ
this()スーパーサイヤ人クラス内の別ルートで登録する
super()サイヤ人戦士としての基本登録を行う

this() と super() はどちらもコンストラクタの先頭に書く必要があります。

そのため、同じコンストラクタの中で次のように両方を書くことはできません。

public SuperSaiyanWarrior(String n, int b, int level)
{
    this();
    super(n, b);
}

どちらも先頭でなければならないため、同時に使えないのです。

Javaは、コンストラクタの最初にどの初期化ルートから始めるのかを1つに決めます。

書き方可能か
先頭に super()可能
先頭に this()可能
this() のあとに super()不可能
super() のあとに this()不可能

親クラスと子クラスのコンストラクタをドラゴンボール風に整理する

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

名前や戦闘力のような、どの戦士にも必要な基本情報を持っています。

SuperSaiyanWarrior は、SaiyanWarrior を受け継いだ子クラスです。

サイヤ人戦士としての基本を持ちながら、スーパーサイヤ人としての変身段階 transformationLevel を追加しています。

オブジェクト作成時には、次の順番で処理が進みます。

順番ドラゴンボール風のイメージJavaの処理
1サイヤ人戦士として登録するSaiyanWarrior のコンストラクタ
2スーパーサイヤ人として変身段階を加えるSuperSaiyanWarrior のコンストラクタ
3完成した戦士として使うメソッド呼び出し

親クラスの土台が先に整い、その上に子クラスの追加要素が乗ります。

この順番を押さえると、継承とコンストラクタの関係がかなり分かりやすくなります。

押さえておきたいポイント

ポイント内容
サブクラスのオブジェクト作成new で通常どおり作成できる
継承したメソッドサブクラスのオブジェクトから呼び出せる
追加したメソッドサブクラス独自の機能として呼び出せる
コンストラクタの順序先にスーパークラス、次にサブクラス
コンストラクタの継承コンストラクタ自体は継承されない
自動的な super()何も書かない場合、親の引数なしコンストラクタが呼ばれる
明示的な super()親クラスの特定のコンストラクタを呼べる
this()同じクラス内の別コンストラクタを呼ぶ
注意点super() と this() はどちらもコンストラクタの先頭に書く

継承は、親クラスの機能を受け継ぐだけでなく、オブジェクト作成時の初期化の順番にも深く関係しています。

親クラスで共通の土台を整え、子クラスで追加の特徴を加える。

この流れが見えると、サブクラスのオブジェクト作成や super() の意味が自然に理解できるようになります。