Java類成員變數、普通成員變數、初始化塊、構造方法的初始化和執行順序
序言
關於類的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 classF 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 publicString 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初始化塊-->初始化父類例項成員變數(如果有賦值語句),執行父類普通初始化塊-->父類構造方法-->初始化子類例項成員變數(如果有賦值語句)及普通初始化塊-->子類構造方法。
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行的註釋去掉後再執行即可說明情況。