SJ blog
beginner
S

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

親コンストラクタはなぜ自動で呼ばれるのか — super()と継承の仕組み

継承したクラスでなぜsuper()が必要なのか、なぜコンストラクタの1行目でなければならないのか、コンストラクタチェーンの仕組みを根本から解説する。

一言結論

継承オブジェクトは親と子の「合体品」。親の部分の初期化は親コンストラクタの責任なので、子のコンストラクタは必ず最初に親を初期化する必要がある。これがsuper()が1行目限定の理由だ。

継承とは「合体」だ

extendsを使うと、親クラスのフィールドとメソッドを引き継いだ子クラスが作れる:

public class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + "が食べた");
    }
}

public class Dog extends Animal {
    String breed;  // 犬種(子クラス固有のフィールド)

    public Dog(String name, String breed) {
        super(name);         // 親コンストラクタを呼ぶ
        this.breed = breed;
    }

    public void bark() {
        System.out.println(name + "がワンと鳴いた");  // 親のnameを参照できる
    }
}

DogAnimalnameフィールドとeat()メソッドを引き継いでいる。

ここで重要なのは、new Dog("ポチ", "柴犬")を実行したとき、メモリに作られるオブジェクトの構造だ:

┌─────────────────────────┐
│   Dogオブジェクト         │
│  ┌───────────────────┐  │
│  │   Animalの部分     │  │
│  │   name = "ポチ"    │  │
│  └───────────────────┘  │
│   breed = "柴犬"         │
└─────────────────────────┘

1つのオブジェクトの中に「Animalの部分」と「Dog固有の部分」が合体している。継承とはこういう構造だ。

親の部分は誰が初期化するのか

問題は「Animalの部分(nameフィールド)を誰が初期化するか」だ。

Dogコンストラクタが直接this.name = nameと書けばいいように思えるが、それには問題がある。Animalnameprivateだったら子クラスからアクセスできない:

public class Animal {
    private String name;  // privateにした場合

    public Animal(String name) {
        this.name = name;
    }
}

public class Dog extends Animal {
    public Dog(String name, String breed) {
        this.name = name;  // ❌ エラー: nameはprivate。Dogからはアクセスできない
    }
}

親クラスの内部(特にprivateなフィールド)の初期化は、その親クラスのコンストラクタだけができることだ。子クラスが親の内部構造を知らなくていいように設計するためにも、親の初期化は親に任せる。

それがsuper(引数)の役割だ:

public Dog(String name, String breed) {
    super(name);   // "AnimalコンストラクタにnameをわたしてAnimalの部分を初期化してくれ"
    this.breed = breed;  // Dog固有の部分はDogが初期化
}

なぜsuper()はコンストラクタの1行目でなければならないのか

public Dog(String name, String breed) {
    this.breed = breed;
    super(name);  // ❌ エラー: super()は必ず1行目
}

なぜ1行目限定なのか?

理由は「親が初期化される前に子のコードが動くと危険だから」だ。

次のケースを考えよう:

public class Animal {
    String name;
    public Animal(String name) { this.name = name; }
    public String getName() { return name; }
}

public class Dog extends Animal {
    String breed;

    public Dog(String name, String breed) {
        // もしsuper()を後回しにできたとしたら...
        System.out.println(getName());  // ← この時点でname = null(親未初期化)
        super(name);  // ← ここで初めて親が初期化される
        this.breed = breed;
    }
}

super()の前にgetName()を呼ぶと、nameはまだnullだ(Animalの部分がまだデフォルト値のまま)。これは危険なバグの温床になる。

「親を最初に完全に初期化し、その後子の初期化を行う」という順序を強制するために、super()は1行目と規定されている。

コンストラクタチェーン

super()を書かなかった場合はどうなるか:

public class Dog extends Animal {
    String breed;

    public Dog(String breed) {
        // super()を書かなかった
        this.breed = breed;
    }
}

この場合、コンパイラが自動的に引数なしsuper()を挿入する:

public Dog(String breed) {
    super();  // ← コンパイラが自動挿入
    this.breed = breed;
}

ただしAnimalに引数なしコンストラクタが存在しない場合はコンパイルエラーになる:

public class Animal {
    String name;
    public Animal(String name) { this.name = name; }
    // 引数なしコンストラクタは定義していない
}

public class Dog extends Animal {
    public Dog(String breed) {
        // super()が自動挿入されるが、Animal()が存在しないのでエラー
    }
}

エラーメッセージは There is no default constructor available in 'Animal' のような内容になる。この場合は明示的にsuper("何か名前")と書く必要がある。

コンストラクタは連鎖して実行される

new Dog()を呼ぶと、コンストラクタは Objectから順番に 実行される:

1. Objectコンストラクタ(すべてのクラスの最終的な親)
2. Animalコンストラクタ
3. Dogコンストラクタ

これをコンストラクタチェーンという。オブジェクトは「根っこから順番に積み上げられる」。

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

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

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

new C();
// 出力:
// A
// B
// C

new C()を呼ぶと最初にAが出力される。Aから始まって積み上げる順番だ。

superでメソッドも呼べる

コンストラクタ以外でもsuperを使って親のメソッドを呼べる:

public class Animal {
    public String describe() {
        return "動物";
    }
}

public class Dog extends Animal {
    @Override
    public String describe() {
        return super.describe() + "(犬)";  // 親の実装を再利用する
    }
}

Dog dog = new Dog();
dog.describe();  // "動物(犬)"

子クラスで親のメソッドを「完全に置き換える」のではなく「親の処理に追記する」場合にsuper.メソッド名()を使う。

まとめ

継承の構造    = 1つのオブジェクトに「親の部分」と「子の部分」が合体している
super()の役割 = 親コンストラクタを呼んで「親の部分」を初期化する
1行目の理由   = 親が初期化される前に子のコードが動くと、親のフィールドがnull/0のまま参照される危険がある
自動挿入      = super()を書かないとコンパイラが引数なしsuper()を挿入する(引数ありコンストラクタのみの親は要明示)
チェーンの順序 = Object → 親 → 子 の順番でコンストラクタが実行される

super()は「儀礼的に書くもの」ではなく、「合体オブジェクトの親部分を正しく初期化するための必然」だ。構造を知ると、なぜこの規則があるのかが自然に理解できる。