プログラマメモ2 - programmer no memo2

[初期化]オブジェクト初期化の流れ 2006/04/05
2006/11/26

オブジェクトの初期化ではまってしまった。



あるオブジェクト(仮にAオブジェクト)が保持しているとメンバのオブジェクト(仮にCオブジェクト)を使用するとjava.lang.NullPointerExceptionが発生してしまう。



CオブジェクトはAオブジェクトのコンストラクタから初期化されるシーケンスになっていた。

ただしAオブジェクトのコンストラクタで直接CをNewしていない。



テストコード記載します。



public class A extends SuperA {



At at = new At();

C c = null;

At at2 = new At();



//コンストラクタを定義されていないので、

//SuperAのデフォルトコンストラクタが呼ばれる。



public void print(){

System.out.println("I am " + getClass().getName());

}



@Override

public void initialize() {

//このメソッドがSuperAのデフォルトコンストラクタから呼ばれる。

c= new C();

}



class At{

public At(){

System.out.println("=== At ===");

}

}

}





/**

*

* 抽象クラス

*

*/

public abstract class SuperA {



/**

* 初期化メソッドを呼び出します。

*/

public SuperA() {

initialize();

}



/**

* 初期化メソッド。 使用者は実装しないといけない。

*

*/

public abstract void initialize();



}



public class C {



public void print(){

System.out.println(">>> OK!!");

}

}



//テスト実行クラス

public class Main {



public static void main(String[] args) {

// 暗黙にデフォルトコンストラクタを呼び出す

A a = new A();

a.c.print();

a.print();

}



}



上記のプログラムは抽象クラスSuperAにはinitializeメソッドが用意されており、抽象クラスSuperAを継承するクラスはinitializeメソッドを実装しないといけません。

initializeメソッドの使用方法を意識しないようにするために、SuperAのデフォルトコンストラクタではinitializeメソッドを呼び出し、ています。



設計としてデザインパターンのテンプレートメソッドパターンを意識したつくりになっていると思います。



この方法が成功するためには、上記のコードの赤文字部分に意図的にnullを入れている部分を止めればうまくいきます。

C c;

にすれば、うまくいきます。



これは、コンストラクタされるシーケンスに注意しないといけない設計になっています。

初期化順序は、



(1)Aのデフォルトコンストラクタが呼ばれる。

(2)SuperAのコンストラクタが呼ばれる。

(3)SuperAからAのinitializeが呼ばれる。フィールド変数cが初期化される。

(4)Aクラスのスコープの初期化(フィールドの)がはじまる。この時に明示的にフィールド変数の初期化を行っていた場合(例えば、オブジェクトにnullを入れる等)、(3)で代入されたオブジェクトが上書きされる。



javaの初期化手順を知っていれば、フィールド変数で定義されたオブジェクトには、暗黙にnullが割り当ててあることをわかるが、フィールド変数にnullを入れてクラスを定義してしまうコーディングをする人は多い。



以上のことから設計の留意点なのだが、

(A)実装者がまちがをおかしずらい設計にする

(B)フィールド変数にはnullを入れて宣言しないコーディング規約を守る、もしくは制定して施行する。



いろいろ反省m(_ _)m

: