Revised: Feb./14th/2003: Since: Dec./30th/2001
コンストラクタは、オブジェクトを生成するための特殊なメソッドのようなものですが、クラスのメンバーではなく、継承はされません。このため、クラスの継承時の振る舞いも、通常のメソッドとは異なり、以下の規則があります。
コンストラクタを明示的に追加しないとき、コンパイラは空のコンストラクタを追加します。このデフォルトのコンストラクタは、次のコードと等価です。
Constructor() {
super();
}
super() は、スーパークラスのコンストラクタ呼び出しです。コンストラクタは、最初にスーパークラスのコンストラクタを実行します。これは言語実装として自動的に行われるので、super() の明示的な呼び出しは、実は不要です。クラスのインスタンス化時には、継承階層の最上位のクラス(java.lang.Object)からサブクラスの末端へ下る順番に構築されていきます。
次のコードは Oya クラスを Ko クラスで継承しています。 Ko クラスのコンストラクタでは明示的に呼び出していないので、自動的にスーパークラスの引数のないコンストラクタが実行されます。
class Oya {
Oya() {
System.out.println("I am Oya!");
}
}
class Ko extends Oya {
Ko() {
// 暗示的にスーパークラスのコンストラクタが実行される
System.out.println("I am Ko!");
}
}
class Main {
public static void main(String[] args) {
Ko obj = new Ko();
}
}
実行結果は次のようになります。
C:\java>java Main.java C:\java>java Main I am Oya! I am Ko.
継承であれば "I am Oya!" は出力されないはずですね?
スーパークラスの暗黙的コンストラクタ呼び出しは、引数のないコンストラクタを呼び出すので、引数を持つコンストラクタだけしか定義していない場合はコンパイルエラーとなります。
class Oya {
Oya(String str) {
System.out.println("My name is " + str + "!");
}
}
class Ko extends Oya {
Ko() {
// 暗示的に Oya() が呼び出される
System.out.println("I am Ko!");
}
}
class Main {
public static void main(String[] args) {
Ko obj = new Ko();
}
}
これをコンパイルすると次のようにコンパイルエラーが報告されます。
C:\java>javac Main.java
Main2.java:8: シンボルを解釈処理できません。
シンボル: コンストラクタ Oya ()
位置 : Oya の クラス
Ko() {
^
エラー 1 個
このコンパイルエラーを回避するためには、次の方法があります。
引数のないコンストラクタを定義することに問題はありません。既に解説済みです。次に、引数を持つコンストラクタの明示的呼び出しについて説明します。
superスーパークラスのコンストラクタは super() で明示的に参照できます。オーバーロード時の this() コンストラクタに似ています。super() コンストラクタを使うときは、サブクラスのコンストラクタの最初の行に記述しなければならないという点も this() と同じです。
先ほどのサンプルは次のように修正できます。
class Oya {
Oya(String str) {
System.out.println("My name is " + str + "!");
}
}
class Ko extends Oya {
Ko() {
super("sugai"); // 明示的スーパークラスコンストラクタ呼び出し
System.out.println("I am Ko!");
}
}
class Main {
public static void main(String[] args) {
Ko obj = new Ko();
}
}
実行結果は次のようになります。
C:\java>javac Main.java C:\java>javac Main My name is sugai! I am Ko!
次のサンプルは this() コンストラクタとの併用の例です。
class Base {
int x;
Base(int a) {
x = a;
}
}
class Sub extends Base {
int y;
Sub() {
this(1, 1); // コンストラクタ内の最初のステートメントであることが必要
System.out.println("デフォルト値 1 を使います。");
}
Sub(int a) {
this(a, 1);
System.out.println("デフォルト値 1 を使います。");
}
Sub(int a, int b) {
super(a); // コンストラクタ内の最初のステートメントであることが必要
y = b;
}
}
Sub クラスをインスタンス化して実行してみてください。
super() も this() もコンストラクタの最初の行に記述しなければなりません。両方記述する必要があるときは、どうすればよいのか。これは愚問です。this() が必要であれば、これを記述し、呼ばれた先のコンストラクタの最初の行で super() を記述しておけば良いからです。
super() のサンプル次の例では、三つのクラスが定義されています。 Oya クラスではコンストラクタが三つオーバーロードされています。これを継承した Ko クラスでは、自身のコンストラクタの中で、スーパークラスのコンストラクタの「int 型引数一つ」のものを呼び出しています。
SuperConstDemo.java:
class Oya {
int x = 0, y = 0;
// コンストラクタ1
Oya() {
this(10, 100);
System.out.println("x, y にはデフォルト値 10, 100 を使います。");
}
// コンストラクタ2
Oya(int a) {
this(a, 100);
System.out.println("y にはデフォルト値 100 を使います。");
}
// コンストラクタ3
Oya(int a, int b) {
x = a;
y = b;
}
}
class Ko extends Oya {
// コンストラクタ
Ko(int a) {
// スーパークラスのコンストラクタ呼び出し
super(a);
}
void getValue() {
// スーパークラスの変数参照
System.out.println("super.x: " + super.x);
System.out.println("super.y: " + super.y);
}
}
class SuperConstDemo {
public static void main(String[] args) {
// インスタンス化
Ko obj = new Ko(5);
// メソッド呼び出し
obj.getValue();
}
}
main() メソッドで Ko クラスをインスタンス化していますが、このとき int 型引数を一つ持つコンストラクタを呼び、5を引き渡しています。呼ばれた Ko クラスのコンストラクタでは、スーパークラスのコンストラクタの int 型引数を一つ持つものを呼んでいます。ここでは、先程 main() メソッドから引き渡された値をそのまま引き渡しています。
このコンストラクタでは、 Oya クラスのメンバ変数を初期化しています。これらの値が、 Ko クラスで定義された getValue() メソッドで出力されます。
C:\java>javac SuperConstDemo.java C:\java>java SuperConstDemo y にはデフォルト値 100 を使います。 super.x: 5 super.y: 100 C:\java>
super()明示的なコンストラクタが作成されていない限りにおいて、引数のないコンストラクタが自動的に作成されます。
サブクラスのコンストラクタ内で明示的に super() が呼ばれていないときには、暗黙的に引数のない super() コンストラクタが呼ばれます。もしスーパークラスで引数のないコンストラクタが存在しないと、コンパイル・エラーになります。
これを回避するには、引数のないコンストラクタをもたないクラスから派生するサブクラスでは、明示的に適切な引数を持った super() を呼び出すか、継承される可能性のあるクラスには引数のないコンストラクタを定義しておくかの何れかです。いずれの場合も、設計の範囲内でありうるケースです。しかし、クラス設計の時には、よしんば無意味であっても、引数のないコンストラクタも用意しておく方が一般的です。