インスタンスフィールドが勝手に上書きされる!?
さて,以下の抽象クラスがあったとする。これは,コンストラクタの中からfoo()メソッドを呼び出すが,foo()メソッドはサブクラスで実装を提供することを意味している。
そして,上記のクラスを継承した下記のクラスがあったとする。ここでは,インスタンスフィールドvalueを宣言し,宣言と同時に値1を代入するように記述している。コンストラクタ内でvalue値を表示し,foo()メソッドの実装では,まずvalue値を表示し,その後valueフィールドに値2を代入している。
さて,「new C2();」と実行したとき,どのような処理結果になるか想像できるだろうか?答えは,これ。
2: 0
1: 1
つまり,
- クラス継承の上位のデフォルトコンストラクタが呼び出される。この際,具象クラスのインスタンスフィールドは評価(実行)されない。
- そして,その後に具象クラスのインスタンスフィールドの式が評価され,コンストラクタが実行される。
というシーケンスとなる(あくまでソースコード上の見かけの動きであり,厳密に言うと↑は嘘)。よって,親クラスのデフォルトコンストラクタから呼び出されたfoo()メソッド内でインスタンスフィールドに値をセットしたとしても,その後にインスタンスフィールドの右辺が評価されるために,上書きされてしまうのだ。
ま,こんな動作は,普通のプログラミングにおいて気にする必要はない。Wicketでは,Componentクラスのインスタンス生成のタイミングでリスナーを登録することが可能である。で,実際にはComponentクラスのデフォルトコンストラクタ内でリスナーの呼び出し処理が走るので,開発者が作成するWebPage,Panel,Formクラスのサブクラスに定義されたインスタンスフィールドは,リスナーの発火時点では初期化されてないから代入しても上書きされちゃうかも,ということになると気がついたという話である。
ちなみに,インスタンスフィールドにfinalをつけておくと,挙動が変化する。例えばC2クラスを,
というように変更してあげれば,「new C2();」の実行結果は,
2: 1
1: 1
というように,foo()メソッドの呼び出しの前に評価が済んでいる値を持った状態となる。ただし,これはインスタンスフィールドの型がプリミティブな場合のみ。オブジェクト型の場合は,finalをつけたとしても,相変わらず初期化が遅延される。
なるほど,奥が深い。
追記(2007/3/28)
odzさんのご指摘通り,思いっきり嘘を書いてしまった。実際には,finalを付けたか付けないかは,初期化の順番が変わるのではなく,定数展開されるかどうかで実行結果が異なってくる,ということである。よく調べずに推測を書いてしまった点を反省。。。orz




このへん関係あります?
http://pmd.sourceforge.net/rules/design.html#ConstructorCallsOverridableMethod
はぃ,というか,まさにそれです (^^;
フィールドの初期化は行われていなくても、abstract メソッドの初期化は行われているというのが面白いですね。
C++ だと、仮想関数が初期化されていないため、失敗したりします。
なるほど,C++だとダメなんですねー。親のコンストラクタからVirtualな関数の呼び出しはダメと。コンパイラに弾かれるのかな?Warning?
Javaの話に戻って,プリミティブ型とオブジェクト型で,finalが付いたインスタンスフィールドの右辺評価のタイミングが異なることに,若干のキモさを感じました。どうせfinalで書き換えられないんだから,プリミティブ型と同じように,デフォルトコンストラクタの実行前に初期化されてもいいじゃん,と。
それをしなかった理由がありそうだけど,思い付きません。。。
[c/c++]C++では不法!?
yoichiroさんの さて,以下の抽象クラスがあったとする。これは,コンストラクタの中からfoo()メソッドを呼び出すが,foo()メソッドはサブクラ…
[Java]Java における定数展開
ref:天使やカイザーと呼ばれて: インスタンスフィールドが勝手に上書きされる!? via:神様なんて信じない僕らのために – C++では不法!? C+…