java 建構函式 成員函式初始化順序 以及多型的建構函式的呼叫順序
對於JAVA中類的初始化是一個很基礎的問題,其中的一些問題也是易被學習者所忽略。當在編寫程式碼的時候碰到時,常被這些問題引發的錯誤,感覺莫名其妙。 而且現在許多大公司的面試題,對於這方面的考查也是屢試不爽。不管基於什麼原因,我認為,對於java類中的初始化問題,有必要深入的瞭解。Java類的 初始化,其實就是它在JVM的初始化問題(類載入的問題),對於它在JVM中的初始化是一個相當複雜的問題,是給專家們來探討的,所以在這裡我只是對一些 容易忽略的問題,發表一下個人觀點:
1,在一個類的內部(不考慮它是另一個類的派生類):很多人認為,類的成員變數是在構造方法呼叫之後再初始化的,先不考慮這種觀點的正確性,先看一下下面的程式碼:
class Test01...{
public Test01(int i)...{
System.out.println("Test01 of constractor : " + i);
}
}
public class Test02 ...{
private Test01 t1 = new Test01(1);
private int n = 10;
public Test02()...{
System.out.println("Test02 of constructor : " + n);
}
private Test01 t2 = new Test01(2 );
public static void main(String[] args) ...{
Test02 test = new Test02();
}
}
輸出的結果為:
Test01 of constractor : 1
Test01 of constractor : 2
Test02 of constructor : 10
通過輸出,可見當生成Test02的例項test時,它並不是首先呼叫其構造方法而是先是成員變數的初始化,而且成員的初始化的順序以成員變數的定義順序有關,先定義的先初始化,初始化後再呼叫構造方法。其實成員變數的初始化,在類的所有方法呼叫之前進行,包括構造方法
當類中有Static 修飾的成員呢?測試下面一段程式碼:
public class Test03 ...{
private int i1 = printCommon();
private static int i2 = printStatic();
public Test03()...{
}
public static int printCommon()...{
System.out.println("i1 is init!");
return 1;
}
public static int printStatic()...{
System.out.println("i2 is init!");
return 2;
}
public static void main(String[] args) ...{
Test03 t = new Test03();
}
}
輸出結果為:
i2 is init!
i1 is init!
可見static的成員比普通的成員變數先初始化。
我們都知道,如果一個類的成員變數沒有在定義時,系統會給予系統預設的值,有=號的就直接給予右值,系統在給予初值和=號給予值這2中方式,在執行時間上有先後嗎?為了測試,我編寫了如下程式碼:
public class Test04 ...{
private static Test04 t1 = new Test04();
private static int i1;
private static int i2 = 2;
public Test04()...{
i1++;
i2++;
}
public static void main(String[] args) ...{
Test04 t2 = new Test04();
System.out.println("t2.i1 = " + t2.i1);
System.out.println("t2.i2 = " + t2.i2);
}
}
我們先預計一下輸出,可能有幾種答案:2和3,3和3,2和2
執行程式碼後:
t2.i1 = 2
t2.i2 = 3
為什麼是2和3呢?其實程式碼的執行順序是這樣的:首先執行給t1,i1,i2分別給予初始值null,0,0,再執行
Test04 t1 =new Test04(),這樣i1++,i2++被執行,i1,i2都變為1,執行完畢後接著執行int i1; i1,i2的值仍然是1,1,當執行int i2 = 2時i2被賦予了值,即i1 = 1,i2=2;再執行Test04 t2 = new Test04(),i1,i2再執行++,此時i1 =2,i2 =3,輸出i1,i2,結果就是:t2.i1 = 2,t2.i2 = 3。 通過上面的程式碼我們可以認為系統預設值的給予比通過等號的賦予先執行。
2,一個類還有上層的類,即父類:
當生成一個子類時,大家到知道會呼叫父類的構造方法。如果子類和父類中都有Static的成員變數呢,其實我們在深入分析一個類的內部初始化後,對於存在父類的類的初始化其實原理都一樣,具體以下面的程式碼為例:
class SuperClass ...{
static...{
System.out.println("SuperClass of static block");
}
public SuperClass()...{
System.out.println("SuperClass of constracutor");
}
}
public class SubClass extends SuperClass...{
static...{
System.out.println("SubClass of static block");
}
public SubClass()...{
System.out.println("SubClass of constracutor");
}
public static void main(String[] args)...{
SuperClass t = new SubClass();
}
}
輸出結果:
SuperClass of static block
SubClass of static block
SuperClass of constracutor
SubClass of constracutor
可見當父類,和子類有Static時,先初始化Static,再初始化子類的Static,再初始化父類的其他成員變數->父類構造方法->子類其他成員變數->子類的構造方法。
父類上層還有父類時,總是先執行最頂層父類的Static-->派生類Static-->派生類 Static-->…….-->子類Static-->頂層父類的其他成員變數-->父類構造方法--> 派生類的其他成員變數 --> 派生類構造方法--> ……………-->子類其他成員變數-->子類構造方法
討論到繼承,就不得提一下多型:
如果父類構造方法的程式碼中有子類中被重寫得方法,當執行這樣的語句
SuperClass super = new SubClass();
初始化時呼叫父類的構造方法,是執行父類的原方法,還是執行子類中被重寫的方法呢?
class SuperClass...{
public SuperClass()...{
System.out.println("SuperClass of constructor");
m();
}
public void m()...{
System.out.println("SuperClass.m()");
}
}
public class SubClassTest extends SuperClass ...{
private int i = 10;
public SubClassTest()...{
System.out.println("SubClass of constructor");
super.m();
m();
}
public void m()...{
System.out.println("SubClass.m(): i = " + i);
}
public static void main(String[] args)...{
SuperClass t = new SubClassTest();
}
}
可能很多人會認為輸出為:
SuperClass of constructor
SubClass.m(): i = 10
SubClass of constructor
SuperClass.m()
SubClass.m(): i = 10
其實不然!
正確輸出為:
SuperClass of constructor
SubClass.m(): i = 0
SubClass of constructor
SuperClass.m()
SubClass.m(): i = 10
在生成物件時,父類呼叫的M()方法,不是父類的 M()方法,而時子類中被重寫了的M()方法!!並且還出現一個怪異的現象,子類的privte int i 也被父類訪問到,這不是和我們說private的 成員只能在本類使用的原則相違背了嗎?其實我們說的這條原則是編譯期間所遵守的,在JAVA程式的編譯期間,它只檢查語法的合法性,在JAVA的JVM 中,即執行期間,不管你宣告的什麼,對於JVM來說都是透明的,而多型是在執行期間執行的,所以能拿到SubClass的private成員,一點都不奇怪,只是此時還沒執行 i = 10,所以在父類的構造方法中呼叫m()時,系統只能將i賦予系統初值0。
下面是我設計的一道完整的初始化例子,可測試你對類的初始化問題是否完整掌握:
寫出程式執行的結果:
class A...{
private int i = 9;
protected static int j;
static...{
System.out.println("-- Load First SuperClass of static block start!-- ");
System.out.println("j = " + j);
System.out.println("-- Load First SuperClass of static block End -- ");
}
public A()...{
System.out.println("------- Load SuperClass of structor start --------");
System.out.println("Frist print j = " + j);
j = 10;
m();
System.out.println("k = " + k);
System.out.println("Second print j = " + j);
System.out.println("----------- Load SuperClass End ----------- ");
}
private static int k = getInt();
public static int getInt()...{
System.out.println("Load SuperClass.getInt() ");
return 11;
}
static...{
System.out.println("--- Load Second SuperClass of static block!-------");
System.out.println("j = " + j);
System.out.println("k = " + k);
System.out.println("-- Load Second SuperClass of static block End -- ");
}
public void m()...{
System.out.println("SuperClass.m() , " + "j = " +j);
}
}
class B extends A ...{
private int a = 10;
static...{
System.out.println("---- Load SubClass of static block!------");
System.out.println("-- Load SubClass of static block End -- ");
}
public B()...{
System.out.println("Load SubClass of structor");
m();
System.out.println("--- Load SubClass End ---- ");
}
public void m()...{
System.out.println("SubClass.m() ," + "a = " + a );
}
}
public class Test1...{
public static void main(String[] args)...{
A a = new B();
}
}
正確的答案為:
-- Load First SuperClass of static block start!--
j = 0
-- Load First SuperClass of static block End --
Load SuperClass.getInt()
--- Load Second SuperClass of static block!-------
j = 0
k = 11
-- Load Second SuperClass of static block End --
---- Load SubClass of static block!------
-- Load SubClass of static block End --
------- Load SuperClass of structor start --------
Frist print j = 0
SubClass.m() ,a = 0
k = 11
Second print j = 10
----------- Load SuperClass End -----------
Load SubClass of structor
SubClass.m() ,a = 10
--- Load SubClass End ----
下面需要說明的一點也是至關重要的一點:那就是成員變數的初始化和非static初始化塊之間的執行順序是按照他們出現的先後順序來執行的
public class Test04
{
//下面的這兩行程式碼放置的順序,跟執行結果是有關係的
private String t1 = test();
{
System.out.println("初始化快!");
}
//上面的這兩行程式碼放置的順序,跟執行結果是有關係的
private String test(){
System.out.println("例項變數的執行過程");
return "test";
}
public Test04()
{
System.out.println("構造方法!");
}
public static void main(String[] args)
{
Test04 t2 = new Test04();
}
}