1. 程式人生 > >【深入Java虛擬機(3)】:類初始化

【深入Java虛擬機(3)】:類初始化

不同 main class out pri ref 另一個 字節碼 被動引用

類初始化是類加載過程的最後一個階段,到初始化階段,才真正開始執行類中的Java程序代碼。虛擬機規範嚴格規定了有且只有四種情況必須立即對類進行初始化:
遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時,如果類還沒有進行過初始化,則需要先觸發其初始化。生成這四條指令最常見的Java代碼場景是:使用new關鍵字實例化對象時、讀取或設置一個類的靜態字段(static)時(被static修飾又被final修飾的,已在編譯期把結果放入常量池的靜態字段除外)、以及調用一個類的靜態方法時。
使用Java.lang.refect包的方法對類進行反射調用時,如果類還沒有進行過初始化,則需要先觸發其初始化。
當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
當虛擬機啟動時,用戶需要指定一個要執行的主類,虛擬機會先執行該主類。
虛擬機規定只有這四種情況才會觸發類的初始化,稱為對一個類進行主動引用,除此之外所有引用類的方式都不會觸發其初始化,稱為被動引用。下面舉一些例子來說明被動引用。
1、通過子類引用父類中的靜態字段,這時對子類的引用為被動引用,因此不會初始化子類,只會初始化父類

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、常量在編譯階段會存入調用它的類的常量池中,本質上沒有直接引用到定義該常量的類,因此不會觸發定義常量的類的初始化

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類的符號引用入口,這兩個類在編譯成Class文件後就不存在任何聯系了。
3、通過數組定義來引用類,不會觸發類的初始化

class Const{
static{
System.out.println("初始化Const類");
}
}

public class ArrayTest{
public static void main(String[] args){
Const[] con = new Const[5];
}
}
執行後不輸出任何信息,說明Const類並沒有被初始化。
但這段代碼裏觸發了另一個名為“LLConst”的類的初始化,它是一個由虛擬機自動生成的、直接繼承於java.lang.Object的子類,創建動作由字節碼指令newarray觸發,很明顯,這是一個對數組引用類型的初初始化,而該數組中的元素僅僅包含一個對Const類的引用,並沒有對其進行初始化。如果我們加入對con數組中各個Const類元素的實例化代碼,便會觸發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類
根據四條規則的第一條,這裏的new觸發了Const類。
最後看一下接口的初始化過程與類初始化過程的不同。
接口也有初始化過程,上面的代碼中我們都是用靜態語句塊來輸出初始化信息的,而在接口中不能使用“static{}”語句塊,但編譯器仍然會為接口生成<clinit>類構造器,用於初始化接口中定義的成員變量(實際上是static final修飾的全局常量)。
二者在初始化時最主要的區別是:當一個類在初始化時,要求其父類全部已經初始化過了,但是一個接口在初始化時,並不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量),才會初始化該父接口。這點也與類初始化的情況很不同,回過頭來看第2個例子就知道,調用類中的static final常量時並不會 觸發該類的初始化,但是調用接口中的static final常量時便會觸發該接口的初始化。

【深入Java虛擬機(3)】:類初始化