Java 類初始化

2018-09-28 19:21 更新

類初始化

類初始化是類加載過(guò)程的最后一個(gè)階段,到初始化階段,才真正開(kāi)始執(zhí)行類中的 Java 程序代碼。虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有四種情況必須立即對(duì)類進(jìn)行初始化:

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

虛擬機(jī)規(guī)定只有這四種情況才會(huì)觸發(fā)類的初始化,稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用,除此之外所有引用類的方式都不會(huì)觸發(fā)其初始化,稱為被動(dòng)引用。下面舉一些例子來(lái)說(shuō)明被動(dòng)引用。

通過(guò)子類引用父類中的靜態(tài)字段,這時(shí)對(duì)子類的引用為被動(dòng)引用,因此不會(huì)初始化子類,只會(huì)初始化父類:

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);  
    }  
}  

執(zhí)行后輸出的結(jié)果如下:

父類被初始化
    33

對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此,通過(guò)其子類來(lái)引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化。

常量在編譯階段會(huì)存入調(diào)用它的類的常量池中,本質(zhì)上沒(méi)有直接引用到定義該常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化:

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);  
    }  
}  

執(zhí)行后輸出的結(jié)果如下:

我是常量

雖然程序中引用了 const 類的常量 NAME,但是在編譯階段將此常量的值“我是常量”存儲(chǔ)到了調(diào)用它的類 FinalTest 的常量池中,對(duì)常量 Const.NAME 的引用實(shí)際上轉(zhuǎn)化為了 FinalTest 類對(duì)自身常量池的引用。也就是說(shuō),實(shí)際上 FinalTest 的 Class 文件之中并沒(méi)有 Const 類的符號(hào)引用入口,這兩個(gè)類在編譯成 Class 文件后就不存在任何聯(lián)系了。

通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)類的初始化:

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

public class ArrayTest{  
    public static void main(String[] args){  
        Const[] con = new Const[5];  
    }  
}  

執(zhí)行后不輸出任何信息,說(shuō)明 Const 類并沒(méi)有被初始化。

但這段代碼里觸發(fā)了另一個(gè)名為“LLConst”的類的初始化,它是一個(gè)由虛擬機(jī)自動(dòng)生成的、直接繼承于java.lang.Object 的子類,創(chuàng)建動(dòng)作由字節(jié)碼指令 newarray 觸發(fā),很明顯,這是一個(gè)對(duì)數(shù)組引用類型的初初始化,而該數(shù)組中的元素僅僅包含一個(gè)對(duì) Const 類的引用,并沒(méi)有對(duì)其進(jìn)行初始化。如果我們加入對(duì) con 數(shù)組中各個(gè) Const 類元素的實(shí)例化代碼,便會(huì)觸發(fā) 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();  
    }  
}  

這樣便會(huì)得到如下輸出結(jié)果:

初始化Const類

根據(jù)四條規(guī)則的第一條,這里的 new 觸發(fā)了 Const 類。

最后看一下接口的初始化過(guò)程與類初始化過(guò)程的不同。

接口也有初始化過(guò)程,上面的代碼中我們都是用靜態(tài)語(yǔ)句塊來(lái)輸出初始化信息的,而在接口中不能使用“static{}”語(yǔ)句塊,但編譯器仍然會(huì)為接口生成類構(gòu)造器,用于初始化接口中定義的成員變量(實(shí)際上是 static final 修飾的全局常量)。

二者在初始化時(shí)最主要的區(qū)別是:當(dāng)一個(gè)類在初始化時(shí),要求其父類全部已經(jīng)初始化過(guò)了,但是一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口中定義的常量),才會(huì)初始化該父接口。這點(diǎn)也與類初始化的情況很不同,回過(guò)頭來(lái)看第 2 個(gè)例子就知道,調(diào)用類中的 static final 常量時(shí)并不會(huì) 觸發(fā)該類的初始化,但是調(diào)用接口中的 static final 常量時(shí)便會(huì)觸發(fā)該接口的初始化。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)