Java繼承中構造器的呼叫原理
Java的繼承是比較重要的特性,也是比較容易出錯的地方,下面這個例子將展示如果父類構造器中呼叫被子類重寫的方法時會出現的情況:
首先是父類:
public class test { void fun(){ System.out.println("test fun()"); } void fun1(){ System.out.println("test fun1()"); } test(){ fun(); fun1(); } public static void main(String[] args) { test t =new test(); } }
這裡父類的構造器將呼叫一個fun方法,main函式的執行結果是:
test fun()
test fun1()
然後是子類:
public class test2 extends test { int i = 2; test2(){ fun(); } void fun(){ System.out.println("test2 fun()"); System.out.println(i); } public static void main(String[] args) { test2 t = new test2(); } }
子類增加了一個欄位i並初始化為2,並重寫了fun方法,不僅列印的字串不一樣,還加了列印i的功能,構造器和父類一樣呼叫了fun方法。main函式的執行結果是:
test2 fun()
0
test fun1()
test2 fun()
2
通常java的類進行初始化的時候,會先進行父類的初始化,所以會先呼叫父類的構造器,再進行子類的初始化,呼叫子類的構造器。
一開始寫完程式碼我以為的結果是:
test fun() test fun1() test2 fun() 2
我以為就算父類的方法被重寫了,也會呼叫自己的方法,但事實告訴我們,父類初始化過程中構造器如果呼叫了被子類重寫的方法,會呼叫被子類重寫的方法。
還有一點,如果子類重寫的方法中使用了子類才定義的欄位,那這個欄位的值將是該欄位型別的預設值。
所以類的初始化流程總結(繼承相關)就是:
1.為物件分配的儲存空間初始化為二進位制零。
2.呼叫父類的構造器,如果呼叫被覆蓋的方法,被覆蓋的方法將被呼叫,如果使用了子類中才定義的欄位,該欄位的值為該欄位型別的預設值。
3.呼叫子類的構造器。
(這裡總結的初始化流程只總結了繼承相關的,正常的static部分、初始化程式碼還是正常的樣子)
為什麼會出現這種情況呢,為什麼需要先為物件的儲存空間初始化為二進位制零呢?
1.在繼承中構造器的呼叫是分級的,先呼叫父類的,父類如果有父類就父類的。。。這一步是通過動態繫結實現的。
2.從概念上來說,構造器是用來初始化物件的,但是像上面那種情況,子類重寫了父類的方法,使得父類將使用子類的成員,但是此時正在初始化父類,子類還沒有進行初始化。
3.基於以上兩點就將為物件分配的儲存空間初始化為二進位制零。
所以重寫父類的方法的時候需要考慮到這個特性,這種特性可能會導致父類的初始化出現問題。
&n