WisdomSoft - for your serial experiences.

5.3 継承とコンストラクタ

継承関係にあるクラスのコンストラクタがどのような手順で呼び出されるのかを紹介します。

5.3.1 スーパークラスの初期化

何らかのクラスを継承したサブクラスが、スーパークラスの初期化も行うのは難しいものがあります。スーパークラスは動作に重要なフィールドを private に設定している可能性がありますし、サブクラスの開発者はスーパークラスの仕様を熟知しなければなりません。再利用に長けたオブジェクト指向の原則は、複雑さの隠蔽にあるのでこれは避けなければなりません。

そこで、サブクラスのコンストラクタの処理が行われる前に、暗黙的にスーパークラスのコンストラクタが呼び出されます。継承関係にあるクラスを正しく初期化するには、最も上のスーパークラスから順にサブクラスを初期化し、逆に破棄するときはサブクラスからスーパークラスへ処理を進めます。Java 言語では、コンストラクタの先頭にスーパークラスのコンストラクタを指定していると考えてよいでしょう。

コード1
class A {
	public A() { System.out.println("A クラスのコンストラクタ"); }
}

class B extends A {
	public B() { System.out.println("B クラスのコンストラクタ"); }
}

class Test {
	public static void main(String args[]) {
		B b = new B();
	}
}
実行結果
>java Test
A クラスのコンストラクタ
B クラスのコンストラクタ

コード1はスーパークラス A と、これを継承したサブクラス B のコンストラクタ呼び出しの関係を調べます。実行結果を見れば、B クラスのインスタンスを生成する時に、先に A クラスのコンストラクタが呼び出され、その後 B クラスが初期化されていることを確認できます。継承関係にあるクラスのインスタンスが生成される時は、スーパークラスのコンストラクタが優先されるのです。そのため、サブクラスの開発者はスーパークラスの初期化を意識する必要はないのです。

しかし、暗黙的に呼び出されるのは常に引数を受け取らないコンストラクタです。スーパークラスの引数を受け取らないコンストラクタが private で隠蔽されている場合、サブクラスはコンストラクタを呼び出せないため、エラーとなってしまいます。そこで、明示的なコンストラクタ起動を用いて呼び出すべきスーパークラスのコンストラクタを選択することができます。サブクラスからスーパークラスのコンストラクタを呼び出すには、次のように記述します。

スーパークラスのコンストラクタ起動
super(引数リスト);

対象がスーパークラスのコンストラクタであることを除いて、同一クラス内のオーバーロードされたコンストラクタを呼び出す代替コンストラクタ起動と同じです。super を使ったこの文を、代替コンストラクタ起動に対してスーパークラスのコンストラクタ起動と呼びます。

コード2
class A {
	private A() {} //呼び出せない
	public A(String out) { System.out.println(out); }
}

class B extends A {
	public B() { super("明示的なコンストラクタ起動"); }
}

class Test {
	public static void main(String args[]) {
		B b = new B();
	}
}
実行結果
>java Test
明示的なコンストラクタ起動

コード2の B クラスのコンストラクタは、スーパークラスのコンストラクタを明示的に起動しています。A クラスの引数を受け取らないコンストラクタは private なので、暗黙的にも明示的にも呼び出すことはできません。そこで、サブクラスからは明示的なコンストラクタの呼び出しで、オーバーロードされたコンストラクタを起動しなければならないのです。A クラスは、引数を受け取らないコンストラクタ以外に String 型を受け取るコンストラクタが宣言されているので、これを明示的に呼び出すことができます。