Java繼承中構造器的調用流程
- 2020 年 3 月 13 日
- 筆記
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.基於以上兩點就將為對象分配的存儲空間初始化為二進制零。
所以重寫父類的方法的時候需要考慮到這個特性,這種特性可能會導致父類的初始化出現問題。