序言
關於類的static成員變量初始化、static初始化塊、實例的普通成員變量初始化、實例的普通初始化塊以及構造函數的執行順序,我一直不是十分明確地知道,今天專門花了幾個小時的時間參考網上資料設計出了比較明了的測試代碼,有代碼有結果有真相。總體而言,static部分執行早於普通初始化塊早於構造函數,如果一個類繼承了某個父類,則父類的static部分最先執行。
正文
測試代碼設計思路:有三個主線類B、C和D,其中D繼承C,C繼承B,這三個類中均包含static塊、普通初始化塊和無參的構造方法;有兩個輔助類E和F,B中包含E類和F類的成員變量,F類成員變量是static類型,E類的成員變量是普通類型;程序運行入口在A.java中,A中的main函數只用來創建D類的實例,其代碼列表如下。
E.java
1 package chloe.executeorder; 2 3 public class E 4 { 5 E() 6 { 7 System.out.println("執行E的構造函數"); 8 } 9 public void funcOfE() 10 { 11 System.out.println("執行E的函數"); 12 } 13 }
F.java
1 package chloe.executeorder; 2 3 public class F 4 { 5 F() 6 { 7 System.out.println("執行F的構造函數"); 8 } 9 public void funcOfF() 10 { 11 System.out.println("執行F的函數"); 12 } 13 }
B.java
1 package chloe.executeorder; 2 3 public class B 4 { 5 E e=new E(); 6 static F f=new F(); 7 public String sb=getSb(); 8 static 9 { 10 System.out.println("執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量)"); 11 f.funcOfF(); 12 } 13 { 14 System.out.println("執行B實例的普通初始化塊"); 15 } 16 B() 17 { 18 System.out.println("執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量)"); 19 e.funcOfE(); 20 } 21 22 public String getSb() 23 { 24 System.out.println("初始化B的實例成員變量sb"); 25 return "sb"; 26 } 27 }
C.java
1 package chloe.executeorder; 2 3 public class C extends B 4 { 5 static 6 { 7 System.out.println("執行C的static塊(C繼承B)"); 8 } 9 { 10 System.out.println("執行C的普通初始化塊"); 11 } 12 C() 13 { 14 System.out.println("執行C的構造函數(C繼承B)"); 15 } 16 }
D.java
1 package chloe.executeorder; 2 3 public class D extends C 4 { 5 public String sd1=getSd1(); 6 public static String sd=getSd(); 7 static 8 { 9 System.out.println("執行D的static塊(D繼承C)"); 10 11 } 12 { 13 System.out.println("執行D實例的普通初始化塊"); 14 } 15 D() 16 { 17 System.out.println("執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:"+sb+";本類D的static成員變量sd的值為:"+sd+";本類D的實例成員變量sd1的值是:"+sd1); 18 } 19 20 static public String getSd() 21 { 22 System.out.println("初始化D的static成員變量sd"); 23 return "sd"; 24 } 25 public String getSd1() 26 { 27 System.out.println("初始化D的實例成員變量sd1"); 28 return "sd1"; 29 } 30 }
A.java
1 package chloe.executeorder; 2 3 public class A 4 { 5 public static void main(String[] args) 6 { 7 System.out.println("運行A中的main函數,創建D類實例"); 8 D d1=new D(); 9 //D d2=new D(); 10 11 } 12 }
執行A.java后運行結果如下圖所示,
1 運行A中的main函數,創建D類實例 2 執行F的構造函數 3 執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量) 4 執行F的函數 5 執行C的static塊(C繼承B) 6 初始化D的static成員變量sd 7 執行D的static塊(D繼承C) 8 執行E的構造函數 9 初始化B的實例成員變量sb 10 執行B實例的普通初始化塊 11 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 12 執行E的函數 13 執行C的普通初始化塊 14 執行C的構造函數(C繼承B) 15 初始化D的實例成員變量sd1 16 執行D實例的普通初始化塊 17 執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:sb;本類D的static成員變量sd的值為:sd;本類D的實例成員變量sd1的值是:sd1
分析:由輸出結果可知,
(1) 整體上先執行static部分(1至7行)后執行非static部分(8至17行)。
(2) 在static部分中先運行父類的后運行子類的。第2行執行F的構造函數是因為B中包含F類的static(靜態)變量,在B.java的第6行調用了F的構造函數初始化static變量;第4行執行F的funcOfF是因為在B的 static初始化塊中調用了這個函數;之后依次執行C和D的static部分。
(3) 在非static部分中也是先運行父類的后運行子類的,對於同一個類,其成員變量的初始化和普通初始化塊的執行優先於構造函數。
對於同一個類,其成員變量初始化一定優先於初始化塊的執行嗎?對於非static的情況,我們將D.java中的第5行移動到普通初始化塊的后面,如下面所示,
1 package chloe.executeorder; 2 3 public class D extends C 4 { 5 6 public static String sd=getSd(); 7 static 8 { 9 System.out.println("執行D的static塊(D繼承C)"); 10 11 } 12 { 13 System.out.println("執行D實例的普通初始化塊"); 14 } 15 public String sd1=getSd1(); 16 D() 17 { 18 System.out.println("執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:"+sb+";本類D的static成員變量sd的值為:"+sd+";本類D的實例成員變量sd1的值是:"+sd1); 19 } 20 21 static public String getSd() 22 { 23 System.out.println("初始化D的static成員變量sd"); 24 return "sd"; 25 } 26 public String getSd1() 27 { 28 System.out.println("初始化D的實例成員變量sd1"); 29 return "sd1"; 30 } 31 }
之后再運行A.java,結果如下,
1 運行A中的main函數,創建D類實例 2 執行F的構造函數 3 執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量) 4 執行F的函數 5 執行C的static塊(C繼承B) 6 初始化D的static成員變量sd 7 執行D的static塊(D繼承C) 8 執行E的構造函數 9 初始化B的實例成員變量sb 10 執行B實例的普通初始化塊 11 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 12 執行E的函數 13 執行C的普通初始化塊 14 執行C的構造函數(C繼承B) 15 執行D實例的普通初始化塊 16 初始化D的實例成員變量sd1 17 執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:sb;本類D的static成員變量sd的值為:sd;本類D的實例成員變量sd1的值是:sd1
該結果的第15和16行的輸出結果與改動之前的輸出結果正好相反,可見成員變量的初始化與普通初始化塊的執行順序不是固定的,它與源代碼中賦值語句及普通初始化塊的放置順序相關:成員變量的賦值語句在前則賦值先執行,普通初始化塊在前則初始化塊先執行。對於static成員變量和static初始化塊的執行順序也是類似的,修改B.java中的第6行的位置即可看到類似的輸出結果,此處不再贅述。
總結
1.執行的大致順序如下,
(1) 在一個不存在繼承的類中:初始化static變量,執行static初始化快-->初始化普通成員變量(如果有賦值語句),執行普通初始化塊-->構造方法
(2)在一個存在繼承的類中:初始化父類static成員變量,運行父類static初始化塊-->初始化子類static成員變量,運行子類static初始化塊-->初始化父類實例成員變量(如果有賦值語句),執行父類普通初始化塊-->父類構造方法-->初始化子類實例成員變量(如果有賦值語句)及普通初始化塊-->子類構造方法。
注意:其中變量初始化(賦值)和初始化塊的執行與相關語句在源碼中的放置順序一致,不過變量聲明會最先執行,參考http://www.189works.com/article-52232-1.html。
2.static成員變量可以再定義的時候初始化也可以在static塊中初始化,static塊可以出現多次,當編譯成.class文件時會將多個static塊的內容合並;實例成員變量可以再定義時初始化也可以在普通初始化塊或構造函數中初始化。
基本數據類型的成員變量要在初始化后再使用,引用數據類型的成員變量在實例化后才能被使用。
3.類的加載時機:
(1) 用new創建該類的實例時;
(2) 使用java.lang.reflect進行反射調用的時候;
(3) 之前沒有加載該類,之后加載該類的子類的時候;
(4) 當虛擬機啟動時,初始化main函數所在的類。
4.JVM載入類時會執行static塊,創建一個實例時會運行構造方法。static塊和static成員變量都是屬於類而非實例的;構造函數和普通成員變量是屬於一個實例的。類的初始化(包括static塊的運行和static成員變量的賦值)只運行一次,多次創建某個類的實例只會運行一次該類的static()塊,但會運行多次其構造函數。將之前的A.java中第9行的注釋去掉后再運行即可說明情況。
