【回転】【Java仮想マシンに深く入り込む】その3:クラス初期化
クラス初期化はクラスロードプロセスの最後のフェーズであり、初期化フェーズになってから、クラス内のJavaプログラムコードの実行が本格的に開始されます.仮想マシン仕様では、クラスをすぐに初期化する必要があるのは4つのみです. new、getstatic、putstatic、invokestaticの4つのバイトコード命令に遭遇した場合、クラスがまだ初期化されていない場合は、まず初期化をトリガーする必要があります.この4つの命令を生成する最も一般的なJavaコードシーンは、newキーワードを使用してオブジェクトをインスタンス化する場合、クラスの静的フィールドを読み取りまたは設定する場合(finalによって修飾され、コンパイル中に結果を定数プールの静的フィールドに入れた場合を除く)、クラスの静的メソッドを呼び出す場合です. Javaを使用する.lang.refectパケットのメソッドがクラスを反射呼び出した場合、クラスがまだ初期化されていない場合は、まず初期化をトリガーする必要があります. 最初にクラスが1つ化されたときに、その親がまだ初期化されていないことが判明した場合は、まずその親の初期化をトリガーする必要があります. 仮想マシンが起動すると、ユーザーは実行するプライマリクラスを指定する必要があります.仮想機会はまずプライマリクラスを実行します.
仮想マシンでは、クラスの初期化がトリガーされるのは、クラスをアクティブに参照することと規定されています.それ以外のすべての参照クラスは、その初期化がトリガーされません.パッシブ参照と呼ばれます.次に、パッシブリファレンスについていくつかの例を挙げて説明します.
1.親の静的フィールドを子から参照する場合、子への参照は受動的に参照されるため、子は初期化されず、親のみが初期化される
[java] view plain copy print ?
class Father{ public static int m = 33;
static{ System.out.println(「親が初期化」);
} }
class Child extends Father{
static{ System.out.println(「サブクラスが初期化される」);
} }
public class StaticTest{
public static void main(String[] args){ System.out.println(Child.m);
} }
実行後に出力される結果は次のとおりです.
親が初期化されました33
静的フィールドの場合、このフィールドを直接定義したクラスのみが初期化されます.したがって、親で定義された静的フィールドを参照すると、親の初期化がトリガーされ、子の初期化はトリガーされません.
2、定数はコンパイル段階で呼び出されたクラスの定数プールに格納され、本質的にその定数を定義するクラスに直接参照されないため、定数を定義するクラスの初期化はトリガーされない
[java] view plain copy print ?
class Const{ public static final String NAME=「私は定数です」
static{ System.out.println(「Constクラスの初期化」)
} }
public class FinalTest{
public static void main(String[] args){ System.out.println(Const.NAME);
} }
実行後に出力される結果は次のとおりです.
私は定数です.
プログラムではconstクラスの定数NAMEを参照しているが、コンパイル段階でこの定数の値「私は定数」を呼び出したクラスFinalTestの定数プールに格納し、定数Const.NAMEの参照は実際にFinalTestクラスの自己定数プールへの参照に変換される.つまり、実際にFinalTestのClassファイルにはConstクラスのシンボルリファレンスエントリはなく、この2つのクラスはClassファイルにコンパイルされてから何の関係もありません.
3、配列定義でクラスを参照し、クラスの初期化をトリガーしない
[java] view plain copy print ?
class Const{ static{
System.out.println(「Constクラスの初期化」) }
}
public class ArrayTest{ public static void main(String[] args){
Const[] con = new Const[5]; }
}
実行後は何も出力されず、Constクラスが初期化されていないことを示します.
しかし、このコードでは、仮想マシンによって自動的に生成され、javaに直接継承される「LLConst」というクラスの初期化がトリガーされます.lang.Objectのサブクラスでは、作成動作がバイトコード命令newarrayによってトリガーされ、これは配列参照タイプの初期化であり、この配列の要素にはConstクラスへの参照が1つしか含まれておらず、初期化されていないことが明らかになった.con配列内の各Constクラス要素のインスタンス化コードを追加すると、次のようにConstクラスの初期化がトリガーされます.
[java] view plain copy print ?
class Const{ static{
System.out.println(「Constクラスの初期化」) }
}
public class ArrayTest{ public static void main(String[] args){
Const[] con = new Const[5]; for(Const a:con)
a = new Const(); }
}
初期化Constクラスは、4つのルールの1つ目に基づいて、ここでnewがConstクラスをトリガーします.
最後に、インタフェースの初期化プロセスとクラスの初期化プロセスの違いを見てみましょう.
インタフェースには初期化プロセスもあります.上記のコードでは静的文ブロックを使用して初期化情報を出力しますが、インタフェースではstatic{}文ブロックは使用できません.コンパイラはインタフェースにクラスコンストラクタを生成し、インタフェースで定義されたメンバー変数(実際にはstatic final修飾のグローバル定数)を初期化します.
両者の初期化の最も主要な違いは、1つのクラスが初期化されたときに、その親クラスがすべて初期化されたことを要求するが、1つのインタフェースが初期化されたときに、その親インタフェースがすべて初期化されたことを要求することではなく、親インタフェースを実際に使用する場合のみ(インタフェースで定義されている定数を参照している場合)のみ、親インタフェースが初期化されます.これもクラス初期化の場合とは異なり、2番目の例を振り返ると、クラスのstatic final定数を呼び出すとクラスの初期化はトリガーされませんが、インタフェースのstatic final定数を呼び出すとインタフェースの初期化がトリガーされます.
仮想マシンでは、クラスの初期化がトリガーされるのは、クラスをアクティブに参照することと規定されています.それ以外のすべての参照クラスは、その初期化がトリガーされません.パッシブ参照と呼ばれます.次に、パッシブリファレンスについていくつかの例を挙げて説明します.
1.親の静的フィールドを子から参照する場合、子への参照は受動的に参照されるため、子は初期化されず、親のみが初期化される
[java] view plain copy print ?
class Father{
static{
}
static{
}
public static void main(String[] args){
}
class Father{
public static int m = 33;
static{
System.out.println(" ");
}
}
class Child extends Father{
static{
System.out.println(" ");
}
}
public class StaticTest{
public static void main(String[] args){
System.out.println(Child.m);
}
}
実行後に出力される結果は次のとおりです.
親が初期化されました33
静的フィールドの場合、このフィールドを直接定義したクラスのみが初期化されます.したがって、親で定義された静的フィールドを参照すると、親の初期化がトリガーされ、子の初期化はトリガーされません.
2、定数はコンパイル段階で呼び出されたクラスの定数プールに格納され、本質的にその定数を定義するクラスに直接参照されないため、定数を定義するクラスの初期化はトリガーされない
[java] view plain copy print ?
class Const{
static{
}
public static void main(String[] args){
}
class Const{
public static final String NAME = " ";
static{
System.out.println(" Const ");
}
}
public class FinalTest{
public static void main(String[] args){
System.out.println(Const.NAME);
}
}
実行後に出力される結果は次のとおりです.
私は定数です.
プログラムではconstクラスの定数NAMEを参照しているが、コンパイル段階でこの定数の値「私は定数」を呼び出したクラスFinalTestの定数プールに格納し、定数Const.NAMEの参照は実際にFinalTestクラスの自己定数プールへの参照に変換される.つまり、実際にFinalTestのClassファイルにはConstクラスのシンボルリファレンスエントリはなく、この2つのクラスはClassファイルにコンパイルされてから何の関係もありません.
3、配列定義でクラスを参照し、クラスの初期化をトリガーしない
[java] view plain copy print ?
class Const{
System.out.println(「Constクラスの初期化」)
}
public class ArrayTest{
Const[] con = new Const[5];
}
class Const{
static{
System.out.println(" Const ");
}
}
public class ArrayTest{
public static void main(String[] args){
Const[] con = new Const[5];
}
}
実行後は何も出力されず、Constクラスが初期化されていないことを示します.
しかし、このコードでは、仮想マシンによって自動的に生成され、javaに直接継承される「LLConst」というクラスの初期化がトリガーされます.lang.Objectのサブクラスでは、作成動作がバイトコード命令newarrayによってトリガーされ、これは配列参照タイプの初期化であり、この配列の要素にはConstクラスへの参照が1つしか含まれておらず、初期化されていないことが明らかになった.con配列内の各Constクラス要素のインスタンス化コードを追加すると、次のようにConstクラスの初期化がトリガーされます.
[java] view plain copy print ?
class Const{
System.out.println(「Constクラスの初期化」)
}
public class ArrayTest{
Const[] con = new Const[5];
a = new Const();
}
class Const{
static{
System.out.println(" Const ");
}
}
public class ArrayTest{
public static void main(String[] args){
Const[] con = new Const[5];
for(Const a:con)
a = new Const();
}
}
では、次のような出力結果が得られます.初期化Constクラスは、4つのルールの1つ目に基づいて、ここでnewがConstクラスをトリガーします.
最後に、インタフェースの初期化プロセスとクラスの初期化プロセスの違いを見てみましょう.
インタフェースには初期化プロセスもあります.上記のコードでは静的文ブロックを使用して初期化情報を出力しますが、インタフェースではstatic{}文ブロックは使用できません.コンパイラはインタフェースにクラスコンストラクタを生成し、インタフェースで定義されたメンバー変数(実際にはstatic final修飾のグローバル定数)を初期化します.
両者の初期化の最も主要な違いは、1つのクラスが初期化されたときに、その親クラスがすべて初期化されたことを要求するが、1つのインタフェースが初期化されたときに、その親インタフェースがすべて初期化されたことを要求することではなく、親インタフェースを実際に使用する場合のみ(インタフェースで定義されている定数を参照している場合)のみ、親インタフェースが初期化されます.これもクラス初期化の場合とは異なり、2番目の例を振り返ると、クラスのstatic final定数を呼び出すとクラスの初期化はトリガーされませんが、インタフェースのstatic final定数を呼び出すとインタフェースの初期化がトリガーされます.