PR

【Java】コンストラクタチェーン(this/super)を1分で解説

Java

Javaでは、コンストラクタ(Constructor)の呼び出しが連鎖して実行される仕組みがあります。これを「コンストラクタチェーン」と呼びます。

結論から言えば、コンストラクタが別のコンストラクタを呼び出すっていうだけです。ですが、深堀すると例えば以下2パターンのコンストラクタチェーンに分けることができたり、それぞれの呼び出し方などに深みがありますので、このページではそこに特化して解説します。

  • 親クラス → 子クラス のコンストラクタチェーン
  • 同じクラス内this(...) 呼び出しによるチェーン

Java Silverなどの資格試験でも差が付きやすい分野の内容です。是非最後までご覧ください。

スポンサーリンク

親クラスと子クラス:superによるコンストラクタチェーン

最も基本的なコンストラクタチェーンは、継承でつながったクラス階層におけるコンストラクタチェーンです。(参考 クラスの継承子クラスのコンストラクタが呼ばれるときは、親クラスのコンストラクタが先に呼ばれるというルールによって繋がるコンストラクタの連鎖です。

class A {
    A() {
        System.out.println("A()");
    }
}

class B extends A {
    B() {
        System.out.println("B()");
    }
}

public class Main {
    public static void main(String[] args) {
        B b = new B();
    }
}

実行フロー

  1. new B()B() コンストラクタに入る。
  2. B() の先頭行に 暗黙的に super() が挿入されており、A() が先に呼ばれる。
  3. A() 内の処理が終わってから、B() 内の処理が行われる。
  4. よって出力は以下の通りとなる。
A()
B()

深堀:「暗黙的に挿入される」super

Javaでは、子クラスのコンストラクタの先頭に super() を明示的に書いていない場合コンパイラが自動で super(); を補ってくれるという仕様があります。

例1:明示しない場合(暗黙に挿入される)

class A {
    A() {
        System.out.println("A()");
    }
}

class B extends A {
    B() {
        System.out.println("B()");
    }
}

この B() の中には super(); が書かれていませんが、実際には以下のように扱われます

B() {
    super(); // ← コンパイラが自動で挿入
    System.out.println("B()");
}

// 結果として、new B() をすると出力は↓:
// A()
// B()

🚫 例2:親クラスに引数付きコンストラクタしかない場合

class A {
    A(int x) {
        System.out.println("A(int)");
    }
}

class B extends A {
    B() {
        System.out.println("B()");
    }
}

この場合はどうなるかというと…コンパイルエラーになります!

なぜなら、コンパイラは B() に対して勝手に super(); を補おうとするのですが、A クラスには引数なしの A() が存在しないからです。

状況コンパイラの動作結果
super(...) が明示的に書かれているそのまま使用OK
super(...) を書いていない暗黙的に super(); を補うOK(親に引数なしがあれば)
親に引数ありコンストラクタしかないのに super() を省略暗黙の super(); がエラー原因にコンパイルエラー

Javaでは親クラスの初期化が必須というルールがあるため、子クラスのコンストラクタが呼ばれるときには必ず最初に親クラスのコンストラクタを呼び出す必要があります。

その「呼び出し忘れ」を防ぐために、「書かなくても super() を勝手に補う」という親切な仕様になっています。

同じクラス内での this によるコンストラクタチェーン

同じクラス内での別コンストラクタを呼びたいときに使うキーワードが this(...) です。

class B {
    B() {
        this(100);                   // 自クラスのコンストラクタ B(int) を呼び出す
        System.out.println("B()");
    }

    B(int x) {
        System.out.println("B(int x) : " + x);
    }
}

実行フロー

  1. new B()B() が呼ばれる。
  2. this(100); により、B(int x) が先に呼ばれる。
  3. B(int x) の処理が終わったら B() に戻って続きが実行される。(↓実行結果)
B(int x) : 100
B()

深堀:this(...) とは?(基本)

this(...) は、同じクラス内の別のコンストラクタを呼び出すためのキーワードです。
これを使うことで、コンストラクタの中から別のコンストラクタを呼び出し、処理を再利用できます。

class Person {
    String name;
    int age;

    // 引数2つのコンストラクタ
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 引数1つのコンストラクタ → this(...) で2つ引数の方を呼び出す
    Person(String name) {
        this(name, 18); // デフォルト年齢を指定
    }
}

📌 this(...) の仕様と制約

特徴説明
コンストラクタの最初の行でしか使えないthis(...) は必ず1行目に書く必要があります。
コンストラクタ間の再利用が目的引数が違うだけで処理が共通な場合などに便利です。
1回だけ呼び出せる連続して this(this(...)) のようなことはできません。

super() と this() の優先順位

Javaのコンストラクタでは、コンストラクタの最初の行で this(...)super(...) のいずれかのみを呼び出せるというルールがあります(どちらも書かない場合は super() が暗黙的に挿入される)。

  • this()super() を同時に書くことはできない。
  • もし this() を書いたら、そこに明示的に書かない限り super()このあと別の場所で呼ばれる(か、暗黙に入る)。

以下のコードを見てください。

class A {
    A() {
        this(2); // 同じクラス内の A(int) を呼ぶ
        System.out.println("A()");
    }

    A(int a) {
        System.out.println("A(int): " + a);
    }
}

class B extends A {
    B() {
        this(4); // 同じクラス内の B(int) を呼ぶ
        System.out.println("B()");
    }

    B(int b) {
        // ここで実際には「super()」が暗黙的に呼ばれる
        System.out.println("B(int): " + b);
    }
}

public class Main {
    public static void main(String[] args) {
        B b = new B();
    }
}
  1. new B()B() コンストラクタへ。
  2. B() の先頭行 this(4) がある → B(int b) を呼びにいく。
  3. B(int b) の先頭に暗黙の super()A() を呼びにいく。
  4. A() の先頭に this(2)A(int a) を呼びにいく。
  5. A(int a) が実行 → 戻って A() の残り → 戻って B(int b) → 戻って B()

この段階での出力順を時系列で並べると

  1. A(int): 2
  2. A()
  3. B(int): 4
  4. B()

つまり、

A(int): 2
A()
B(int): 4
B()

という順に出力されます。つまり、thisが呼ばれたらsuperはそこでは呼ばれない(=thisが呼んだ先のコンストラクタで呼び出される)ということ。

super()this() はどちらもコンストラクタチェーンを構成する重要な要素ですが、「最初の1行にしか書けない」という共通ルールのもと、それぞれが持つ役割と呼び出し順に違いがありますthis() を使えば、自クラス内の別のコンストラクタを経由して super() にたどり着く構造になるため、直感とは異なる実行順になることも。設計やデバッグ時には、「どこで親の初期化が走るか?」を正確に把握することが、思わぬバグを防ぐカギになります。

コンストラクタチェーンのまとめ

  1. 継承関係 では「子クラスのコンストラクタ → 親クラスのコンストラクタ」の順で呼ばれる。実際には、子クラスのコンストラクタの先頭super()(暗黙または明示)を呼び出すため。
  2. 同じクラス内 で複数のコンストラクタがある場合、this(...) で別のコンストラクタへ処理が飛ぶ。その中でさらに親クラスへの super() が呼ばれる流れができる。
  3. ルール:「コンストラクタの最初の行で this(...) or super(...) のどちらか1つだけ」が適用される。書かない場合は自動で super() が補われる。
  4. 書き方のコツ:一度 this(...) が挟まると、自分クラスの別のコンストラクタへ飛んだ上で super() が呼ばれるので、呼び出し順が直感とズレる可能性がある。あらかじめ意識しておこう。

◆ ひとことアドバイス

コンストラクタの処理順はバグの原因になりやすいポイントです。「コンストラクタを呼ぶと、どこからどこへ飛んで、どのタイミングで親クラスが初期化されるのか」を、一度紙に書いて整理してみると理解が深まります。もし、「あれ、なんでこのメッセージが先に出るんだろう?」と思ったら、コンストラクタチェーンを疑ってログ出力を確認するのがおすすめです。

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