めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Javaクラス変数初期化のタイミング

柄にもなく(?)Javaネタです。。。

クラス変数にシングルトンをもたせる場合、コンストラクタ内で未初期化のクラス変数へのアクセスが発生する可能性があります。このあたりの確認です。

クラス変数としてシングルトンを生成・格納する場合

StaticNewInstance.java

package sandbox;

public class StaticNewInstance {
	static {
		System.out.println("Start: Initialize static");
	}
	private static StaticNewInstance instance = new StaticNewInstance();
	private static Object o = new Object();
	static {
		System.out.println("Done: Initialize static >> " + o);
	}

	private StaticNewInstance() {
		System.out.println("Start: Constructor");
		System.out.println("Done: Constructor >> " + this.o);
	}

	public static void main(String[] args) {
		System.out.println("- MAIN");   
	}
}

実行結果

Start: Initialize static
Start: Constructor
Done: Constructor >> null
Done: Initialize static >> java.lang.Object@246bc73b
- MAIN

クラスがロードされたタイミングで、クラス変数の初期化中にコンストラクタが呼ばれ、コンストラクタ内部で未初期化のクラス変数への参照が発生しています。

外部から普通にインスタンスを生成する場合

NewInstance.java

package sandbox;

public class NewInstance {
	static {
		System.out.println("Start: Initialize static");
	}
	public static Object o = new Object();
	static {
		System.out.println("Done: Initialize static >> " + o);
	}

	public NewInstance() {
		System.out.println("Start: Constructor");
		System.out.println("Done: Constructor >> " + this.o);
	}
}

Test.java

package sandbox;

public class Test {
	public static void main(String[] args) {
  		System.out.println("- MAIN");
  		System.out.println("Start: Create instance");
  		NewInstance instance = new NewInstance();
  		System.out.println("Done: Create instance >> " + instance.o );
	}
}

実行結果

- MAIN
Start: Create instance
Start: Initialize static
Done: Initialize static >> java.lang.Object@5270cdd2
Start: Constructor
Done: Constructor >> java.lang.Object@5270cdd2
Done: Create instance >> java.lang.Object@5270cdd2

インスタンス生成に伴ってクラスがロードされると、クラス変数の初期化が終わった後にコンストラクタが呼び出されています。したがって、未初期化のクラス変数へのアクセスは発生しません。

Innerクラスのクラス変数にシングルトンを格納する場合

InnerStaticInstance.java

package sandbox;

public class InnerStaticInstance {
	static {
		System.out.println("Start: Outer initialze static");
	}
	private static class Inner {
		static {
			System.out.println("Start: Inner initialze static");
		}
		private final static InnerStaticInstance outer = new InnerStaticInstance();
		static {
			System.out.println("Done: Inner initialze static >> " + outer.o);
		}
	}
	static Object o = new Object();
	static {
		System.out.println("Done: Outer initialze static");
	}
	
	public static InnerStaticInstance getInstance() { 
		System.out.println("Start: getInstance");
		InnerStaticInstance singleton = Inner.outer;
		System.out.println("Done: getInstance >> " + singleton.o);
		return singleton;
	}

	private InnerStaticInstance() {
		System.out.println("Start: Constructor");
		System.out.println("Done: Constructor >> " + this.o);
	}

	public static void main(String[] args) {
		System.out.println("- MAIN");
		InnerStaticInstance.getInstance();
	}
}

実行結果

Start: Outer initialze static
Done: Outer initialze static
- MAIN
Start: getInstance
Start: Inner initialze static
Start: Constructor
Done: Constructor >> java.lang.Object@545eb748
Done: Inner initialze static >> java.lang.Object@545eb748
Done: getInstance >> java.lang.Object@545eb748

クラスロード時には、シングルトンの生成は行われずに、Outerクラスのクラス変数初期化が完了します。getInstance()のタイミングで、Innerクラスの初期化が行われて、Outerクラスのシングルトンの生成(Outerクラスのコンストラクタの実行)が行われます。つまり、Outerクラスのコンストラクタ実行時には、Outerクラスのクラス変数初期化は終わっており、未初期化のクラス変数へのアクセスは発生しません。

もちろん、次のように、Outerクラスのコンストラクタ内部で、Innerクラス内のクラス変数を参照すると未初期化の変数を参照させることは可能です。Innerクラスは、あくまでシングルトンの生成のためだけに使用する(その他のクラス変数などの余計な構造はもたせない)という前提になります。

InnerStaticInstance.java

package sandbox;

public class InnerStaticInstance {
	static {
		System.out.println("Start: Outer initialze static");
	}
	private static class Inner {
		static {
			System.out.println("Start: Inner initialze static");
		}
		private final static InnerStaticInstance outer = new InnerStaticInstance();
		static Object o = new Object();
		static {
			System.out.println("Done: Inner initialze static >> " + outer.o);
		}
	}
	static Object o = new Object();
	static {
		System.out.println("Done: Outer initialze static");
	}
	
	public static InnerStaticInstance getInstance() { 
		System.out.println("Start: getInstance");
		InnerStaticInstance singleton = Inner.outer;		
		System.out.println("Done: getInstance >> " + singleton.o);
		return singleton;
	}

	private InnerStaticInstance() {
		System.out.println("Start: Constructor");
		System.out.println("Inner static >> " + Inner.o);
		System.out.println("Done: Constructor >> " + this.o);
	}

	public static void main(String[] args) {
		System.out.println("- MAIN");
		InnerStaticInstance.getInstance();
	}
}

実行結果

Start: Outer initialze static
Done: Outer initialze static
- MAIN
Start: getInstance
Start: Inner initialze static
Start: Constructor
Inner static >> null
Done: Constructor >> java.lang.Object@23f9e6e5
Done: Inner initialze static >> java.lang.Object@23f9e6e5
Done: getInstance >> java.lang.Object@23f9e6e5