“コンストラクタ”と初期化、本当に理解できてる?【改訂版】Eclipseではじめるプログラミング(13)(2/3 ページ)

» 2009年12月17日 00時00分 公開
[小山博史株式会社ガリレオ]

「this」キーワードとは、「シャドウ」とは

 ここで、フィールドnを初期化するに当たり「this.n = n;」としています。「this」キーワードは、自分自身を参照する際に使われます。

 ここでは、コンストラクタの仮パラメータ名で使われているnと、クラスのフィールドとして定義されている変数nを区別するために、「=」の左辺を「this.n」としています。

 コンストラクタやメソッドにおいて、フィールド名と仮パラメータ名に同じ変数名が付いていた場合は、その変数名を使うと、フィールドは見えなくなって、仮パラメータの方が使われます。この状況を「シャドウ」といいます。ですから、自分自身のフィールドへアクセスしたい場合は、自分自身を表す「this」キーワードを使うのです。

「this呼び出し」とは

 引数を取るコンストラクタを定義した場合、「引数なしのコンストラクタ」は暗黙のうちに定義されなくなります。このため、引数なしのコンストラクタが必要な場合は、次のsample13.ConstructorTestBase2クラスのように明示的に定義が必要です。

 次の例の場合は、引数なしのコンストラクタと、int型の引数を取るコンストラクタ、2つが定義されています。コンストラクタの最初の処理には、「this呼び出し」が使えます。コードにある「this()」が「this呼び出し」です。ここの例では、引数なしのコンストラクタで、int型の引数を取るコンストラクタを利用しています。

 先ほど、「this」キーワードは自分自身を参照することを説明しました。「this呼び出し」も同じように理解しておけばいいでしょう。「this」キーワードを使うと、自分自身にすでに定義されているコンストラクタを呼び出せます。

package sample13;
class ConstructorTestBase2 {
    int n;
    ConstructorTestBase2() {
        this(0);
        System.out.println("ConstructorTestBase2:引数なしのコンストラクタ");
    }
        ConstructorTestBase2(int n) {
        super();
        this.n = n;
        System.out.println("ConstructorTestBase2:" + n);
    }
}
ConstructorTestBase2.java

コストラクタの呼び出し方

 作成したクラスの動作を確認するクラスを用意します。各クラスのコンストラクタを呼び出しているだけですが、これで、コンストラクタがどういった処理をしているか、様子が分かります。

package sample13;
public class Sample01 {
    public static void main(String[] args) {
        System.out.println("ConstructorTestBase()  :-----");
        ConstructorTestBase c = new ConstructorTestBase();
        System.out.println("ConstructorTestBase1(1):-----");
        ConstructorTestBase1 c1 = new ConstructorTestBase1(1);
        System.out.println("ConstructorTestBase2() :-----");
        ConstructorTestBase2 c2 = new ConstructorTestBase2();
        System.out.println("ConstructorTestBase2(5):-----");
        c2 = new ConstructorTestBase2(5);
    }
}
Sample01.java

 実行結果は、次のとおりです。コンストラクタが呼び出されていることが分かります。

ConstructorTestBase()  :-----
ConstructorTestBase1(1):-----
ConstructorTestBase1:1
ConstructorTestBase2() :-----
ConstructorTestBase2:0
ConstructorTestBase2:引数なしのコンストラクタ
ConstructorTestBase2(5):-----
ConstructorTestBase2:5
Sample01.javaの実行結果

 sample13.ConstructorTestBaseクラスでは、暗黙のコンストラクタにより、コンストラクタを定義しなくてもインスタンスの初期化ができています。sample13.ConstructorTestBase1クラスでは、引数として1を渡したので、それを使って初期化されたことが分かります。

 sample13.ConstructorTestBase2クラスの最初の初期化では、引数なしのコンストラクタを呼んだ場合、引数ありのコンストラクタへ0を渡して呼び出したことが分かります。2つ目の初期化では、引数ありのコンストラクタへ5を渡して呼び出したことが分かります。

スーパークラスのコンストラクタは、どうなっている?

  あるクラスのインスタンス初期化については基本的に、そのクラスにコーディングされたコンストラクタによって決まります。このコンストラクタがどのように実装されているかは、ソースコードを見ない限り分かりません。つまり、スーパークラスで宣言されているフィールドの初期化については、スーパークラスしか知らないのです。

 ですから、あるクラスを拡張したクラスを作成する場合は、スーパークラスのコンストラクタを意識する必要があります。最初にスーパークラス内のフィールド初期化をするために、「スーパークラスのコンストラクタからどれかを選んで呼び出した後、拡張した分の初期化をする」という手順を踏む必要があるからです。その初期化が終わってから、自分のクラスのオブジェクトが持つフィールドについて初期化をします。

スーパークラスのインスタンス初期化の順序

 クラスのインスタンスが生成されたときの初期化の順番については、先ほど説明しました。「スーパークラスのコンストラクタを呼び出す」と、次のように、クラスのインスタンス初期化と同様な順番で初期化がされます。java.lang.Objectクラスのコンストラクタが呼び出されるまで、これが繰り返されます。

  1. スーパークラスのインスタンス変数はデフォルトの値で初期化
  2. スーパークラスのスーパークラスのコンストラクタを呼び出す
  3. スーパークラスのインスタンス変数に対して明示的に指定された初期化子と初期化ブロックで初期化
  4. スーパークラスのコンストラクタで指定された処理で初期化

 ここまで、スーパークラスのコンストラクタを呼び出す処理は明示的に書きませんでしたが、この場合は、スーパークラスの「引数なしのコンストラクタ」が暗黙の内に呼び出されます。

暗黙のうちにスーパークラスのコンストラクタが呼ばれる?

 Javaでは、クラス宣言時にextendsを使ってスーパークラスを指定しない場合は、暗黙のうちにjava.lang.Objectクラスを拡張することを説明しました。また、コンストラクタを用意しない場合は、「引数なしのコンストラクタ」が暗黙のうちに用意されることも説明しました。これらを考え合わせると、sample13.ConstructorTestBaseクラスのコードは、次と同じ内容だということになります。

package sample13;
class ConstructorTestBase extends java.lang.Object {
    ConstructorTestBase() {
        super();
    }
}

「super()」って何?

 ここで、「super()」というものが出てきました。前回説明した「super」キーワードはスーパークラスのインスタンスを表しますが、「super()」を使うとスーパークラスのコンストラクタを明示的に呼び出せます。スーパークラスに「引数なしのコンストラクタ」がない場合は、明示的にスーパークラスのコンストラクタを呼び出す必要があります。

“暗黙”のコンストラクタを実際に呼び出すと……

 実際に暗黙のうちにスーパークラスのコンストラクタが呼ばれることを確認するには、次のようなクラスを用意します。ConstructorTestBase2には、「引数なしのコンストラクタ」が定義されていますから、それが呼び出されます。

package sample13;
class ConstructorTest extends ConstructorTestBase2 {
 
}
ConstructorTest.java

明示的に、引数ありのsuper()を指定

 明示的に、super()を指定することもできます。コンストラクタはメソッドではないので継承されませんから、引数ありのコンストラクタで、スーパークラスの同じようなコンストラクタを呼び出したいときは、明示的に呼び出しする必要があります。

package sample13;
class ConstructorTest1 extends ConstructorTestBase1 {
    int n;
    ConstructorTest1() {
        super(10);
        System.out.println("ConstructorTest1:" + n);
    }
}
ConstructorTest1.java

 ついでに、this呼び出しを使った場合にどうなるかも、確認できるようにしておきましょう。

package sample13;
class ConstructorTest2 extends ConstructorTestBase2 {
    int n;
    ConstructorTest2() {
        super();
        System.out.println("ConstructorTest2:");
    }
    ConstructorTest2(int n) {
        super(n);
        System.out.println("ConstructorTest2:" + n);
    }
    ConstructorTest2(int m, int n) {
        this(m*n);
        System.out.println("ConstructorTest2(m*n):" + m*n);
    }
}
ConstructorTest2.java

Eclipseでスーパークラスからコンストラクタを簡単に自動生成Eclipseでスーパークラスからコンストラクタを簡単に自動生成

 Eclipseでは、スーパークラスからコンストラクタも簡単に生成できます。

  1. クラスのソース内にマウスカーソルをおいて右ボタンをクリックしてメニューを表示
  2. [ソース]→[スーパークラスからコンストラクターを生成]を指定(図3
  3. [スーパークラスからコンストラクターを生成]ダイアログが表示されるので、適切な値を指定
  4. [OK]をクリック
図3 [スーパークラスからコンストラクターを生成] 図3 [スーパークラスからコンストラクターを生成]

 このようにEclipseの機能を使うと、スーパークラスからコンストラクタを生成してから、必要な処理を追加するだけで済みます。

 次ページでは、スーパークラスのコンストラクタについて、さらに解説します。 “隠蔽”なども出てくるので、混乱するかもしれませんが、じっくりと読んで考えれば理解できると思います。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。