SJ blog
beginner
S

信頼度ランク

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

コンストラクタのthis()とsuper()の制約 — なぜ1行目限定で両立不可なのか

this()とsuper()がコンストラクタの1行目限定である理由、なぜ両立できないのか、循環コンストラクタ呼び出しをコンパイラが検出する仕組み、privateコンストラクタの意味を解説。

一言結論

this()とsuper()は「初期化の順序保証」のために1行目限定。this()はsuper()の別ルートへの委譲なので両立させると二重初期化になる。循環呼び出しはコンパイラが静的に検出して防ぐ。

2つの制約をおさらい

コンストラクタには2つの特殊な呼び出し構文がある:

public class Child extends Parent {
    public Child() {
        super();   // ← 親コンストラクタを呼ぶ(1行目のみ)
        // ...
    }
}

public class MyClass {
    public MyClass() {
        this(42);  // ← 同クラスの別コンストラクタを呼ぶ(1行目のみ)
        // ...
    }

    public MyClass(int value) {
        // ...
    }
}

なぜどちらも1行目のみなのかは親コンストラクタの記事で説明した。ここではさらに細かい制約を掘り下げる。

this()とsuper()は両立できない

public class Child extends Parent {
    public Child() {
        super(1);    // ❌ コンパイルエラー
        this(2);     // ← どちらも1行目にしか書けない
    }
}

当たり前に見えるかもしれないが、「どちらかしか書けない」のにはちゃんと理由がある。

this()は「同クラスの別のコンストラクタに委譲する」。委譲された先のコンストラクタが最終的にsuper()を呼ぶ。つまり**this()を経由しても必ずsuper()は呼ばれる**:

public class MyClass extends Parent {
    public MyClass() {
        this(42);       // MyClass(int)に委譲
        // MyClass(int)の中でsuper()が呼ばれる
    }

    public MyClass(int value) {
        super();        // ← ここでsuper()が呼ばれる
        this.value = value;
    }
}

もしthis()super()を両方書けたとしたら:

public Child() {
    super(1);   // 親コンストラクタを呼ぶ
    this(2);    // さらに別のコンストラクタを呼ぶ → そこでもsuper()が呼ばれる
}

親コンストラクタが2回呼ばれることになる。親が初期化処理を2回行うと、状態がおかしくなる可能性がある。これを防ぐため「this()super()のどちらか1つ、1行目に」というルールになっている。

循環するthis()呼び出しはコンパイルエラー

this()で別のコンストラクタに委譲するとき、循環することはできない:

public class MyClass {
    public MyClass() {
        this(1);    // MyClass(int)を呼ぶ
    }

    public MyClass(int value) {
        this();     // MyClass()を呼ぶ ← 循環!
    }
}
コンパイルエラー: recursive constructor invocation

MyClass()MyClass(int)MyClass()MyClass(int) → … と無限に繰り返す。コンパイラはコンストラクタ間の呼び出しグラフを静的に解析し、ループがあればコンパイル時点でエラーにする。実行時まで待たずに防げるのがポイントだ。

super()は書かなくても呼ばれる(ただし条件付き)

super()を書かなかった場合、コンパイラが引数なしsuper()を自動挿入する:

public class Dog extends Animal {
    public Dog() {
        // super() を書かなかった
        this.name = "犬";
    }
    // ↓ コンパイラが変換する
    public Dog() {
        super();  // 自動挿入
        this.name = "犬";
    }
}

ただしthis()を書いた場合は自動挿入されない。委譲先のコンストラクタがsuper()を持つはずだからだ:

public Dog() {
    this("ポチ");   // super()の自動挿入はされない
}

public Dog(String name) {
    super();        // ← ここで初めてsuper()が(自動挿入または明示的に)呼ばれる
    this.name = name;
}

privateコンストラクタとは何か

コンストラクタにもprivateがつけられる:

public class Singleton {
    private static Singleton instance;

    private Singleton() { }  // ← privateコンストラクタ

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();  // クラス内部からは呼べる
        }
        return instance;
    }
}
Singleton s = new Singleton();  // ❌ エラー: コンストラクタがprivate
Singleton s = Singleton.getInstance();  // ✅ これが唯一の方法

privateコンストラクタが使われる主な場面:

シングルトンパターン:インスタンスを1つだけ作りたいとき。外部からnewできなくし、staticメソッドで1つのインスタンスを管理する。

ファクトリメソッドパターンnewの代わりにstaticメソッドでインスタンスを返す。名前をつけられる・キャッシュできる・サブクラスを返せる、といった柔軟性がある:

public class Connection {
    private Connection() { }

    public static Connection forDatabase(String url) {
        Connection c = new Connection();
        // DB接続の初期化
        return c;
    }

    public static Connection forTest() {
        Connection c = new Connection();
        // テスト用の初期化
        return c;
    }
}

ユーティリティクラスMathクラスのようにstaticメソッドしかないクラスはインスタンスを作る意味がない。誤ってnew Math()されないようにprivateにする。

privateコンストラクタでもthis()での委譲は同クラス内なので使える。super()は親クラスのコンストラクタ呼び出しなので制限はない。

コンストラクタでやってはいけないこと

試験でよく出る細かいルールをまとめる:

public class Example {
    final int value;

    public Example() {
        // super()よりも前にthis.value = 1 は書けない
        // (super()の前はほぼ何も書けない)
        super();
        value = 1;  // ← finalフィールドはここで初期化できる
    }

    // ❌ abstractコンストラクタは存在しない
    // abstract Example();  → コンパイルエラー

    // ❌ staticコンストラクタも存在しない
    // static Example();    → コンパイルエラー

    // ❌ 戻り値の型を書くとコンストラクタではなくなる
    // void Example() { }   → これはExampleという名前のメソッド
}

「戻り値型を書くとコンストラクタではなくなる」は特に要注意だ。コンパイルエラーにならず、new時に呼ばれないメソッドとして静かに存在してしまう:

public class Trap {
    int value;

    void Trap() {          // ← コンパイラはこれをメソッドとして扱う
        this.value = 99;
    }
}

Trap t = new Trap();
System.out.println(t.value);  // 0(コンストラクタとして呼ばれていないので初期化されていない)

まとめ

this()とsuper()が1行目限定 = 初期化の順序を保証するため(既存記事で解説)
両立不可の理由             = this()は最終的にsuper()を呼ぶ別ルート。両方書くと親コンストラクタが2回実行される
循環this()                = コンパイラが静的に検出してコンパイルエラー
super()の自動挿入          = super()未記載なら引数なしを自動挿入。ただしthis()がある行には挿入しない
privateコンストラクタ       = 外部からのnewを禁止。シングルトン・ファクトリ・ユーティリティクラスに使う
戻り値型を書いたらメソッド  = void Example(){}はコンストラクタではなくメソッド。newで呼ばれない