一、java對象初始化過程
第一步,加載該類,一個java對象在初始化前會進行類加載,在JVM中生成Class對象。加載一個類會進行如下操作,下面給出遞歸描述。(關於Class對象詳見反射 點擊這里)
如果該類有父類,則先加載其父類。
i 初始化該類靜態成員
ii 執行該類靜態代碼塊
第二步,創建對象,如果該類有父類,則創建對象時會先創建其父類的對象,外層包裹子類的屬性和方法,然后返回子類的引用,下面給出遞歸描述。
如果該類有父類,先創建父類的對象。
i 初始化該類普通成員。
ii 執行普通代碼塊。
iii 調用該類構造方法。
二、案例測試
該類對象作為成員變量
public class Info{ public Info(String s) { System.out.println(s); } }
父類
public class Parent { public static Info info = new Info("Parent static member"); //靜態成員 public Info info2 = new Info("Parent common member"); //普通成員 static { //靜態代碼塊 System.out.println("parent static block"); } { //普通代碼塊 System.out.println("parent common block"); } public Parent() { //父類構造方法 System.out.println("Parent.Parent()"); } }
子類
public class Child extends Parent{ public static Info info = new Info("Child static member"); //靜態成員 public Info info2 = new Info("Child common member"); //普通成員 static { //靜態代碼塊 System.out.println("Child static block"); } { //普通代碼塊 System.out.println("Child common block"); } public Child() { //子類構造方法 System.out.println("Child.Child()"); } }
下面測試類的加載過程,我們不創建對象,而是直接加載類,並且是加載子類
public class InitObjectTest{ public static void main(String[] args) { try{ //Class.forName("Parent"); Class.forName("Child"); }catch(Exception e){ } //System.out.println("=============== now , we create an Object below ==========="); //new Parent(); } }
測試結果:
測試結果符合上面所寫的加載類的規則,先初始化父類靜態成員,再執行父類靜態塊,然后初始化子類靜態成員,最后執行子類靜態塊。我們可以看到靜態成員確實在類加載時初始化。
注意:類的加載只進行一次,之后創建對象將不再進行類加載,這也是為什么靜態代碼塊只執行一次的原因。
下面,將父類加載與創建父類對象分開,觀察測試結果
public class InitObjectTest{ public static void main(String[] args) { try{ //Class.forName("Parent"); Class.forName("Parent"); }catch(Exception e){ } System.out.println("=============== now , we create an Object below ==========="); new Parent(); } }
測試結果:
測試結果符合上面的規則,我們先顯示的加載了Parent類,所以后面在new Parent()時就沒有再加載類了。在創建對象時,先初始化普通成員,再執行普通代碼塊,最后調用構造方法。
下面加上子類進行測試。
public class InitObjectTest{ public static void main(String[] args) { try{ //Class.forName("Parent"); //Class.forName("Parent"); }catch(Exception e){ } System.out.println("=============== now , we create an Object below ==========="); new Child(); } }
測試結果:
當我們沒有顯示的加載類時,new對象時,會自動加載類。而輸出的前四行就是,加載類的反應。后面的六行是創建對象的反應,先初始父類的普通成員,再執行父類的普通代碼塊,然后調用父類構造方法,然后進行子類的類似操作。完全符合上面描述的創建過程。
下面測試,先加載父類,然后直接創建子類對象。
public class InitObjectTest{ public static void main(String[] args) { try{ //Class.forName("Parent"); Class.forName("Parent"); }catch(Exception e){ } System.out.println("=============== now , we create an Object below ==========="); new Child(); } }
測試結果:
首先就加載了父類,在創建子類對象時需要加載子類,加載子類時,需要加載父類,而父類在之前就已經加載過了,所以這里並沒有再次加載。
三、總結
到此,靜態成員、靜態代碼塊、普通成員、普通代碼塊、構造方法以及父類的這些模塊之間的執行時序就講完了。分成加載和創建兩個步驟來看,十分清晰,每個步驟中又涉及父類的加載,這是一個遞歸的過程。成員的初始化在代碼塊的執行之前,因為代碼塊可能會操作成員。代碼塊常常用於初始化成員。
本文個人編寫,水平有限,如有錯誤,懇請指出,歡迎討論分享