【java解惑】クラスとインスタンスの初期化サイクル問題


次のコードを示します.
public class Example049 {

	private int overtime;
	public static Example049 INSTANCE = new Example049();//1
	private static int CURRENT_YEAR = Calendar.getInstance().get(
			Calendar.YEAR);//2

	private Example049() {
		overtime = CURRENT_YEAR - 1970;
	}

	public int getOverTime() {
		return overtime;
	}

	public static void main(String[] args) {
		System.out.println(INSTANCE.getOverTime());
	}
}

結果の説明:
出力結果は-1970で、1と2を位置を入れ替えると、出力結果は現在の年から1970の差--45を減算します.
    
コード解析:
このプログラムで発生する問題は,クラス初期化順序のループに起因する.クラスの初期化は、仮想マシンによるmainメソッドの呼び出しによってトリガーされます.まず、静的ドメインがデフォルト値に設定され、INSTANCEドメインがnull、CURRENT_に設定されるYEARは0に設定され、overtimeは0に設定されます.次に、静的ドメインイニシエータは、その出現順に初期化を実行する.静的ドメインINSTANCEの値は、コンストラクタを呼び出すことによって算出される.このコンストラクタは静的ドメインCURRENT_を使用します.YEARの式はovertimeを初期化します.通常、静的ドメインを読み込むとクラスが初期化されるイベントの1つになりますが、クラスを初期化しているので、再帰的な初期化の試みは直接無視されます.静的ドメインが初期化される前に、その値を読み出す可能性がありますが、この静的ドメインには、その属するタイプのデフォルト値しか含まれていません.よって、CURRENT_YEARの値はデフォルト値0のままです.これは、結果が-1970になった理由です.
クラス初期化におけるループによる問題は診断しにくいが,診断されると通常は訂正しやすい.クラス初期化サイクルを修正するには、各初期器がそれに依存する初期器の前に現れるように、静的ドメインの初期器を並べ替える必要があります.2と1を位置を入れ替え、静的ドメインCURRENT_YEARが先に初期化され、INSTANCEが初期化されるとCURRENT_YEARは正しい値を持っているので、出力結果は正しいです.
いくつかの一般的な設計モードは本質的に初期化サイクルであり、特に本題で示す単一例モード(Singleton)とサービスプロバイダフレームワーク(Service Provider Framework)である.タイプの安全な列挙モード(Typesafe Enum pattern)もクラス初期化のサイクルを引き起こす.
要するに,クラス初期化サイクルに注意する.最も簡単なループは、単一のクラスにのみ関連しますが、複数のクラスにも関連します.クラス初期化サイクルも必ずしも悪いことではありませんが、静的ドメインが初期化される前にコンストラクタを呼び出す可能性があります.静的ドメイン、さらにはfinalタイプの静的ドメインは、初期化される前にデフォルト値が読み出される可能性があります.
    
次のコードクリップを参照してください.
// 1
public class Example051Parent {

	protected int x;

	Example051Parent(int x) {
		this.x = x;
		output();
	}

	protected void output() {
		System.out.println(x);
	}
}
// 2
public class Example051Sub extends Example051Parent {
	String f;

	Example051Sub(int x, String f) {
		super(x);
		this.f = f;
	}

	@Override
	protected void output() {
		System.out.println(x + "--" + f);
	}

	public static void main(String[] args) {
		new Example051Sub(1, "ape_it");
	}
}

上記のコードクリップの出力は1−nullである.このコードフラグメントが最初のコードフラグメントと異なるのは、文字列fがインスタンスドメインであり、静的ドメインではないことである.インスタンスドメインが割り当てられる前に、その値を使用する可能性がありますが、この場合もその属するタイプのデフォルト値が含まれています.これは静的ドメイン(クラスドメイン)と同じです.この2つの例では、クラスの初期化サイクルとインスタンスの初期化サイクルの両方で初期化サイクルが生成されます.ただし、ループのクラス初期化は避けられない災害であるが、ループのインスタンス初期化は常に可能であり、常に回避すべきであることに注意してください.
いずれにしても、コンストラクタがサブクラスに上書きされたメソッドを呼び出すと、このように呼び出されたメソッドはインスタンスが初期化される前に常に実行されるため、この問題が発生します.この問題を回避するには、コンストラクタで上書き可能なメソッドを呼び出して直接呼び出したり間接呼び出したりしないでください.この禁止は、インスタンス初期器と擬似コンストラクタ(readObjectとclone)に拡張する必要があります.これらの方法は、コンストラクタを呼び出さずにオブジェクトを作成できるため、擬似コンストラクタと呼ばれます.いずれの場合も、コンストラクタで上書き可能なメソッドを呼び出さないでください.インスタンスの初期化で生成されるループは致命的です.
注:本「java解惑」シリーズは、ブロガーが「java解惑」の原書を読んだ後、原書の説明と例の部分を改編し、博文として発表したものです.すべての例はgithubで直接テストに合格し、共有されています.これらの例を通じて自分を励まして他人に恩恵を与える.また、このシリーズのすべてのブログは、ブロガー個人の微信公衆番号で「愛題猿」や「ape_it」を検索して読むことができます.もし文章の中に原作者の権利を侵害する内容があれば、直ちにブロガーに知らせて、直ちに削除してください.もし読者が文章の中の内容に異議があれば、ブログの伝言や微信の公衆番号の伝言などの方法で共同で検討することを歓迎します.
ソースアドレスhttps://github.com/rocwinger/java-disabuse
本文は“winger”のブログから出て、転載をお断りします!