信頼度ランク
| 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で呼ばれない