[初期化]オブジェクト初期化の流れ
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
あるオブジェクト(仮に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
: