1. 程式人生 > >Java虛擬機 - 類初始化

Java虛擬機 - 類初始化

結果 信息 nbsp 程序代碼 也有 編譯期 說明 加載 指令

【深入Java虛擬機】之三:類初始化

類初始化是類加載過程的最後一個階段,到初始化階段,才真正開始執行類中的Java程序代碼。虛擬機規範嚴格規定了有且只有四種情況必須立即對類進行初始化:

  • 遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時,如果類還沒有進行過初始化,則需要先觸發其初始化。生成這四條指令最常見的Java代碼場景是:使用new關鍵字實例化對象時、讀取或設置一個類的靜態字段(static)時(被static修飾又被final修飾的,已在編譯期把結果放入常量池的靜態字段除外)、以及調用一個類的靜態方法時。
  • 使用Java.lang.refect包的方法對類進行反射調用時,如果類還沒有進行過初始化,則需要先觸發其初始化。
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
  • 當虛擬機啟動時,用戶需要指定一個要執行的主類,虛擬機會先執行該主類。

虛擬機規定只有這四種情況才會觸發類的初始化,稱為對一個類進行主動引用,除此之外所有引用類的方式都不會觸發其初始化,稱為被動引用。下面舉一些例子來說明被動引用。

1、通過子類引用父類中的靜態字段,這時對子類的引用為被動引用,因此不會初始化子類,只會初始化父類

[java] view plain copy
  1. class Father{
  2. public static int m = 33;
  3. static{
  4. System.out.println("父類被初始化");
  5. }
  6. }
  7. class Child extends Father{
  8. static{
  9. System.out.println("子類被初始化");
  10. }
  11. }
  12. public class StaticTest{
  13. public static void main(String[] args){
  14. System.out.println(Child.m);
  15. }
  16. }

執行後輸出的結果如下:

父類被初始化
33

對於靜態字段,只有直接定義這個字段的類才會被初始化,因此,通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化。

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

[java] view plain copy
  1. class Const{
  2. public static final String NAME = "我是常量";
  3. static{
  4. System.out.println("初始化Const類");
  5. }
  6. }
  7. public class FinalTest{
  8. public static void main(String[] args){
  9. System.out.println(Const.NAME);
  10. }
  11. }

執行後輸出的結果如下:

我是常量

雖然程序中引用了const類的常量NAME,但是在編譯階段將此常量的值“我是常量”存儲到了調用它的類FinalTest的常量池中,對常量Const.NAME的引用實際上轉化為了FinalTest類對自身常量池的引用。也就是說,實際上FinalTest的Class文件之中並沒有Const類的符號引用入口,這兩個類在編譯成Class文件後就不存在任何聯系了。

3、通過數組定義來引用類,不會觸發類的初始化

[java] view plain copy
  1. class Const{
  2. static{
  3. System.out.println("初始化Const類");
  4. }
  5. }
  6. public class ArrayTest{
  7. public static void main(String[] args){
  8. Const[] con = new Const[5];
  9. }
  10. }

執行後不輸出任何信息,說明Const類並沒有被初始化。

但這段代碼裏觸發了另一個名為“LLConst”的類的初始化,它是一個由虛擬機自動生成的、直接繼承於java.lang.Object的子類,創建動作由字節碼指令newarray觸發,很明顯,這是一個對數組引用類型的初初始化,而該數組中的元素僅僅包含一個對Const類的引用,並沒有對其進行初始化。如果我們加入對con數組中各個Const類元素的實例化代碼,便會觸發Const類的初始化,如下:

[java] view plain copy
  1. class Const{
  2. static{
  3. System.out.println("初始化Const類");
  4. }
  5. }
  6. public class ArrayTest{
  7. public static void main(String[] args){
  8. Const[] con = new Const[5];
  9. for(Const a:con)
  10. a = new Const();
  11. }
  12. }

這樣便會得到如下輸出結果:

初始化Const類
根據四條規則的第一條,這裏的new觸發了Const類。

最後看一下接口的初始化過程與類初始化過程的不同。

接口也有初始化過程,上面的代碼中我們都是用靜態語句塊來輸出初始化信息的,而在接口中不能使用“static{}”語句塊,但編譯器仍然會為接口生成<clinit>類構造器,用於初始化接口中定義的成員變量(實際上是static final修飾的全局常量)。

二者在初始化時最主要的區別是:當一個類在初始化時,要求其父類全部已經初始化過了,但是一個接口在初始化時,並不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量),才會初始化該父接口。這點也與類初始化的情況很不同,回過頭來看第2個例子就知道,調用類中的static final常量時並不會 觸發該類的初始化,但是調用接口中的static final常量時便會觸發該接口的初始化。

Java虛擬機 - 類初始化