instance field の initialize のタイミング in id:ys_y

某師*1の日記に継承関係のあるクラスのオブジェクト生成時の初期化のタイミングについてかかれているのを見て、触発されて以下のようなテストコードを動かしました。

abstract class Parent {
  public Parent() {
    System.out.println( ">>In parent constructor." );
    init();
  }

  protected abstract void init();
}

class Child extends Parent {
  private int field = 1;

  {
    System.out.println( ">>In instance initializer." );
    System.out.println( "field: "+field );
    field = 2;
  }

  Child() {
    super();
    System.out.println( ">>In child constructor." );
    System.out.println( "field: "+field );
    field = 4;
  }

  protected void init() {
    System.out.println( ">>In child init method." );
    System.out.println( "field: "+field );
    field = 3;
  }

  void print() {
    System.out.println( ">>In child print method." );
    System.out.println( "field: "+field );
  }
}

public class Test1 {
  public static void main( String[] args ) {
    (new Child()).print();
  }
}

で、結果ですが

>>In parent constructor.
>>In child init method.
field: 0
>>In instance initializer.
field: 1
>>In child constructor.
field: 2
>>In child print method.
field: 4

となりました。
つまり、呼び出し順としては

  1. 親コンストラクタ内の処理
  2. 子initメソッド(オーバーライドされたメソッド)
  3. 子フィールド初期値代入処理
  4. インスタンスイニシャライザ
  5. 子コンストラクタ内の処理

ということになります。
ここでちょっと不思議なのが、親コンストラクタ内で初期化のすんでいない子クラスのinitメソッドを呼び出せるということ。でも、そのあとに子の初期化処理が動いているという・・・(initメソッド内でのfieldの値が 0 というのがなんとも・・・)
C++だと、仮想関数テーブルの初期化がすんでいない状態な気もします。

気になったんで C++ でもやってみました。使用コンパイラBorland C++ 5.5.1 です。
以下、ソース。

#include 

using std::cout;
using std::endl;

class Parent {
public:
  Parent() {
    cout << ">>In parent constructor." << endl;
    init();
  }

  virtual void init()=0;
};

class Child : public Parent {
private:
  int field;
public:
  Child() : Parent(), field( 1 ) {
    cout << ">>In child constructor." << endl;
    cout << "field: " << field << endl;
    field = 2;
  }

  void init() {
    cout << ">>In child init method." << endl;
    cout << "field: "<< field << endl;
    field = 3;
  }

  void print() {
    cout << ">>In print method." << endl;
    cout << "field: "<< field << endl;
  }
};

void main() {
  Child child;
  child.print();
}

で、実行結果はというと・・・

>>In parent constructor.

Pure virtual function called

と怒られちゃいました。C++の場合、親クラスのコンストラクタ内では、仮想関数テーブルが子クラスの仮想関数へのポインタで初期化される前のため、呼び出しに失敗します。
もし、Parent::init が純粋仮想関数ではなく実装をもつ仮想関数だった場合は Parent::init での処理が行なわれることになります。子クラスでオーバーライドしているつもりが、望んだ動作をしてくれないということになります。

まとめると

  • Javaはオーバーライドされたメソッド呼び出しについては親クラスのコンストラクタから呼び出し可能。子クラスのフィールドは親クラスのコンストラクタ処理が終了後、初期化される。
  • C++は親クラスのコンストラクタからオーバーライドされた子クラスの仮想関数を呼び出すことはできない。

ってことで。