クラスの初期化を検討するとき、サブクラスの初期化を実行するとき、親クラスが初期化されていない場合、サブクラスを最初に初期化する必要があることを知っています。ただし、物事は単一の文ほど単純ではありません。
まず、Javaでトリガーされる初期化の条件を見てみましょう。
(1)新しいオブジェクトをインスタンス化して静的データとメソッドにアクセスするために、つまり、指示に遭遇した場合:新しい、GetStatic/Patstatic、Invokestatic。
(2)クラスを呼び出すために反射を使用する場合。
(3)クラスを初期化するとき、親クラスが初期化されていない場合、親クラスの初期化が最初にトリガーされます。
(4)エントランスメインメソッドが実行されるクラスが配置されています。
(5)メソッドハンドルがJDK1.7の動的言語サポートに配置されているクラス。初期化がトリガーされていない場合。
コンパイル後、<クリニット>メソッドが生成され、この方法でクラスの初期化が実行されます。この方法は実行されるだけで、JVMはこれを保証し、同期制御を実行します。
その中で、メソッド呼び出しの観点から、条件(3)は、最初に親クラス<クリニット>を再帰的に呼び出すサブクラス<クリニット>です。
ただし、「トリガー」が初期化を完了しないことに注意する必要があります。つまり、サブクラスの初期化は、「危険」がある親クラスの初期化の前に事前に終了する可能性があります。
1。クラスの初期化の例:
この例では、周辺クラスを使用して、相続関係のある2つの静的メンバークラスを含みます。周辺クラスと静的メンバークラスの初期化には因果関係がないため、このように見せることは安全で便利です。
親クラスAとチャイルドクラスBには、それぞれメイン機能が含まれています。上記のトリガー条件(4)から、これら2つの主要な関数をそれぞれ呼び出すことにより、異なるクラス初期化パスがトリガーされることがわかります。
この例の問題は、親クラスにチャイルドクラスの静的参照が含まれており、定義でそれを初期化することです。
public class wrapperclass {private static class a {static {system.out.println( "class a初期化開始..."); } //親クラスには、子供クラスの静的参照が含まれていますprivate static b b = new b();保護された静的int aint = 9; static {system.out.println( "クラスA初期化終了..."); } public static void main(string [] args){}} private static class bは{static {system.out.println( "class b initialization start ..."); } //サブクラスのドメインは、親クラスのドメインに依存しますprivate static int bint = 9 + a.aint; public b(){//コンストラクターの静的ドメインは、クラスsystem.out.println( "bint" + bintのクラスB " +"値のコンストラクターコール)に依存します。 } static {system.out.println( "クラスB初期化の終了..." + "aint:" + bint); } public static void main(string [] args){}}}シナリオ1:出力結果エントリがクラスBの主な機能である場合:
/** *クラスAの初期化が始まります... *クラスBのコンストラクターは、BINT 0の値を呼び出します *クラスA初期化の終了... *クラスBの初期化が始まります... *クラスB初期化... AINT値:18 */
分析:メイン関数の呼び出しがクラスBの初期化をトリガーし、クラスBの<クリニット>メソッドに入ることがわかります。クラスAは、最初に初期化を開始し、aの<クリニット>メソッドに入り、新しいB()のステートメントがあります。この時点で、Bはインスタンス化されます。これはすでにクラスBの<クリニット>にあります。メインスレッドはロックを取得し、クラスBの<クリニット>の実行を開始しました。最初に、JVMはクラスの初期化方法が1回しか実行されないことを保証すると述べました。新しい命令を受信した後、JVMはクラスBの<クリニット>メソッドを再度入力しませんが、直接インスタンス化されます。ただし、現時点では、クラスBがクラスの初期化を完了していないため、BINTの値は0であることがわかります(この0は、クラスロードの準備段階でメソッド領域のメモリを割り当てた後に実行されたゼロ初期化です)。
したがって、親クラスには子タイプの静的ドメインを含み、割り当てアクションを実行すると結論付けることができます。これにより、クラスの初期化が完了する前にサブクラスのインスタンス化が実行される可能性があります。
シナリオ2:エントリがクラスAの主な関数である場合の出力結果:
/** *クラスAの初期化が始まります... *クラスBの初期化が始まります... *クラスBの初期化の終わり...
分析:シナリオ1の分析の後、クラスBの初期化によるクラスAの初期化をトリガーすると、クラスBの初期化が完了する前にクラスAのクラス変数Bのインスタンス化が実行されることがわかります。したがって、クラスAが最初に初期化された場合、クラス変数インスタンス化のときにクラスBを最初にトリガーできます。答えはイエスですが、まだ問題があります。
出力によれば、クラスAの初期化が完了する前にクラスBの初期化が実行されることがわかります。これにより、クラスBが初期化された後にクラス変数のような変数が初期化されることがわかります。
結論:要約すると、親クラスにサブクラスタイプのクラス変数を含め、それらを定義するときにそれらをインスタンス化することは非常に危険であると結論付けることができます。特定の状況は、例ほど簡単ではないかもしれません。定義で値を割り当てるためのメソッドを呼び出すことも危険です。サブクラスタイプの静的ドメインを含める場合でも、静的メソッドを介して値を割り当てる必要があります。JVMは、静的メソッドが呼び出される前にすべての初期化アクションが完了することを確認できるためです(もちろん、この保証は静的b b = new b();そのような初期化挙動を含めるべきではありません)。
2。インスタンス化された例:
まず、オブジェクト作成のプロセスを知る必要があります。
(1)新しい命令に遭遇したとき、クラスが荷重、検証、準備、解析、および初期化が完了したかどうかを確認します(解析プロセスは、シンボル参照を直接参照に解析することです。たとえば、メソッド名はシンボル参照です。これは、このシンボル参照を使用するときに実行できます。
(2)メモリを割り当て、フリーリストまたはポインター衝突法を使用し、新しく割り当てられたメモリを「ゼロ」します。したがって、すべてのインスタンス変数は、このリンクのデフォルト(nullと呼ばれる)によって0に初期化されます。
(3)親クラスの<init>メソッド(コンストラクター)への呼び出し、インスタンス変数によって定義された割り当てアクション、インスタンティエーターがインスタンティエーターで実行される、最後にコンストラクターのアクションを呼び出すなど、<init>メソッドを実行します。
この例は、よりよく知られているかもしれません。つまり、「コンストラクター、クローンメソッド、およびreadObjectメソッドで過剰なメソッドを呼び出さないでください」に違反します。その理由は、Javaの多型、つまり動的結合です。
親クラスAのコンストラクターには保護された方法が含まれており、クラスBはサブクラスです。
Public Class誤解{private static class a {public a(){dosomething(); } protected void dosomething(){system.out.println( "a's dosomething"); }} private static class bはa {private int bint = 9; @Override Protected void dosomething(){system.out.println( "b's dosomething、bint:" + bint); }} public static void main(string [] args){b b = new b(); }}出力結果:
/** * bのdosomhinge、bint:0 */
分析:まず第一に、ディスプレイがない場合、Javaコンパイラがデフォルトのコンストラクターを生成し、最初に親クラスのコンストラクターを呼び出すことを知る必要があります。したがって、クラスBのコンストラクターは、最初にクラスAのコンストラクターを最初に呼び出します。
保護された方法は、クラスAで呼び出されます。出力結果から、サブクラスのメソッド実装が実際に呼び出され、サブクラスのインスタンス化はまだ開始されていないため、Bintは「予想」として9ではなく0です。
これは動的な結合のためであり、dosomhotingは保護された方法であるため、オブジェクトインスタンスのタイプに基づいて対応するメソッド実装を見つけるInvokeVirtual指令を通じて呼び出されます(以下はBのインスタンスオブジェクトであり、対応する方法はクラスBのメソッド実装です)。
結論:前述のように、「コンストラクター、クローンメソッド、およびreadObjectメソッドの過剰な方法を呼び出さないでください」。
上記は、Javaクラスの初期化とインスタンス化の2つの「地雷原」です。みんなの学習に役立つことを願っています。