Javaクラスのロードの初期化段階について説明します。

12886 ワード

クラスローディングの初期化段階は、クラス変数に正しい値を与えます。主に二つの初期化方式があります。一つはクラス変数による初期化文です。一つは静的初期化文です。下記のコードに示すように、前者はクラス変数初期化文であり、後者は静的初期化文である。
public class Example1 {
    static int width;
    static int height = (int) (Math.random() * 2.0);

    static {
        width = (int) (3 * Math.random() * 5.0);
    }
}
すべてのクラス変数初期化語句と静的初期化語句はJavaコンパイラによって集められ、一つの特殊な方法に入れられます。クラスにとって、この方法はクラス初期化方法と呼ばれ、インターフェースについては、この方法をインターフェース初期化方法と呼ぶ。Java classファイルでは、クラスとインターフェースの初期化方法が統一されて()方法と呼ばれています。この方法はJava仮想マシンでしか起動できません。Javaプログラムは起動できません。一つのクラスを初期化するには、二つのステップが含まれています。クラスにスーパークラスがある場合、クラス初期化方法があるなら、この方法を実行して一つのインターフェースを初期化するのは一つのステップだけです。
注意:初期化クラスのプロセスは同期を維持しなければならない。複数のスレッドが一つのクラスを初期化すると、一つのスレッドだけが初期化を実行することができ、他のスレッドは待つ必要がある。
  • ()方法Java方法は、クラス変数初期化語句と静的初期化語句のコードをJava classファイルの()に置く。たとえば、Example 1.javaのclassソースファイルを見ると、()は以下のように見える。
      0: invokestatic  #2                  // Method java/lang/Math.random:()D
      3: ldc2_w        #3                  // double 2.0d
      6: dmul
      7: d2i
      8: putstatic     #5                  // Field height:I
     11: ldc2_w        #6                  // double 3.0d
     14: invokestatic  #2                  // Method java/lang/Math.random:()D
     17: dmul
     18: ldc2_w        #8                  // double 5.0d
     21: dmul
     22: d2i
     23: putstatic     #10                 // Field width:I
     26: return
    
    ()方法は、まずクラス変数初期化文を実行し、heightを初期化し、次いで静的初期化文を実行し、widthを初期化した。すべてのクラスが()方法を持っているわけではなく、以下の3つの場合はクラスがクラス変数を説明していないし、静的初期化文もない。クラス変数を説明しましたが、クラス変数初期化文がありません。ページには静的初期化文がありません。クラスの近くに静的なfinal変数のクラス変数初期化文が含まれています。コンパイル時の定数です。Example 2.java
    public class Example2 {
        static final int angle = 35;
        static final int length = angle * 2;
    }
    ()方法はありません。Example 3.java
    public class Example3 {
        static final int angle = (int) (35 * Math.random());
        static final int length = angle * 2;
    }
    ()の方法があります。Math.randomはコンパイラの定数ではないので、angleとlengthは初期化します。
     0: ldc2_w        #2                  // double 35.0d
     3: invokestatic  #4                  // Method java/lang/Math.random:()D
     6: dmul
     7: d2i
     8: putstatic     #5                  // Field angle:I
    11: getstatic     #5                  // Field angle:I
    14: iconst_2
    15: imul
    16: putstatic     #6                  // Field length:I
    19: return
    
    インターフェースに対してExample 4.java
    public interface Example4 {
        int angle = (int) (35 * Math.random());
        int length = 2;
    }
    インターフェースの変数はデフォルトではpublic static finalです。
    public static final int angle;
        descriptor: I
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    
      public static final int length;
        descriptor: I
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: int 2
    
    また、コンパイル期間定数2の前に値を付けても、()方法は発生しません。しかし、変数のangleのために()方法を生成します。彼の値は運行期間によって決定されます。
     0: ldc2_w        #1                  // double 35.0d
     3: invokestatic  #3                  // Method java/lang/Math.random:()D
     6: dmul
     7: d2i
     8: putstatic     #4                  // Field angle:I
    11: return
    
    次に、Java仮想マシンはいつクラス変数を初期化しますか?2.アクティブに使って、受動的に使うなら、Java仮想マシンはいつクラス変数を初期化しますか?アクティブに使うときは初期化します。アクティブに使う場合は全部で6つあります。
    1)クラスの新しいインスタンスを作成します。2)クラスの静的な方法を呼び出す(すなわち、バイトコードinvokestatic命令を実行する)。3)クラスまたはインターフェースの静的フィールドを使用して、またはそのフィールドに値を割り当て(すなわちバイトコードgetstatic、putstatic命令を実行する)、finalで修飾された静的フィールドを除いて、コンパイル時の定数式に初期化されたためです。4)Java APIにおける反射方法5を呼び出して、あるクラスのサブクラスを初期化する(あるクラスは初期化されています。スーパークラスはすでに初期化されています。6)仮想マシンは、ある種類の起動クラスを起動します。
    受動的な使用、サブインターフェースまたは親インターフェースの非常量静的変数を使用します。このサブクラスまたはサブインターフェースは初期化されません。すなわち、アクティブ使用中のフィールドは、親または親のインターフェースではなく、このクラスまたはインターフェースによって明示的に示されなければならない。この例を見てください。
    public class NewParent {
        static int hoursOfSleep = (int) (Math.random()*3.0);
        static {
            System.out.println("NewParent was initialized");
        }
    }
    
    public class NewbornBaby extends NewParent {
    
        static int hoursOfCrying = (int) (6 + (int)( Math.random() * 2.0));
    
        static {
            System.out.println("NewbornBaby was initialized");
        }
    }
    テストコード1:
    public class Example5 {
    
        public static void main(String[] args) {
            int hours = NewbornBaby.hoursOfSleep;
        }
    
        static {
            System.out.println("Example5 was initialized");
        }
    }
    出力結果:
    Example5 was initialized
    NewParent was initialized
    NewbornBabyが呼び出すフィールドは自分のものではなく、親のものであるため、受動的な参照が発生しました。NewbornBabyは初期化しないし、ロードされる必要もないです。Example 5とNewPartが初期化されるだけです。NewPartent初期化は、NewbornBaby親タイプの初期化ですか?それともフィールドを呼び出して初期化しますか?次の例を見てコードをテストします。
    public class Temp {
        static int tempValue = (int) (Math.random()*3.0);
        static {
            System.out.println("Temp was initialized");
        }
    }
    public class Example5 {
    
        public static void main(String[] args) {
            int hours = Temp.tempValue;
        }
    
        static {
            System.out.println("Example5 was initialized");
        }
    }
    テスト結果:
    Example5 was initialized
    Temp was initialized
    フィールドが呼び出されたために初期化が行われていることが分かります。
    次に、アクティブ引用の例を見て、テストコード3:
    public class Example5 {
    
        public static void main(String[] args) {
            int hours = NewbornBaby.hoursOfCrying;
        }
    
        static {
            System.out.println("Example5 was initialized");
        }
    }
    出力結果:
    Example5 was initialized
    NewParent was initialized
    NewbornBaby was initialized
    NewbornBabyは自身のフィールドを呼び出すため、アクティブ参照が発生し、Newborn Babyを初期化しますが、初期化する前に親クラスを初期化しなければならないため、NewPartも初期化されます。
    自動的に静的であり、finalであり、コンパイル時定数式を使用して初期化する場合、このようなフィールドを使用することは、フィールドの所在クラスのアクティブな使用ではない。Javaコンパイラはこのようなフィールドを定数のローカルコピーに解析します。テストコード4:
    public class Angry {
        static final String greeting = "hello world!";
        static {
            System.out.println("Angry is initialized");
        }
    }
    public class Example6 {
        public static void main(String[] args) {
            String str = Angry.greeting;
        }
    
        static {
            System.out.println("Example6 was initialized");
        }
    }
    テスト結果:
    Example6 was initialized
    クラスAngryは初期化プロセスを実行していません。