在面試的時候,有時候我們會被問到這樣的問題:子類A繼承父類B,A a = new A();則父類B的構造函數、父類B靜態代碼塊、父類B非靜態代碼塊、子類A構造函數、子類A靜態代碼塊、子類A非靜態代碼塊執行的先后順序是什么?
我們先根據上面的題目,可以寫出如下代碼:
父類B代碼如下:
public?class?B?{
public?B(){
System.out.println("父類B的構造函數");
}
static?{
System.out.println("父類B的中的靜態代碼塊");
}
{
System.out.println("父類B的中的非靜態代碼塊?sya()");
}
}
子類A代碼如下:
public?class?A?extends?B{
public?A(){
System.out.println("子類A的構造函數");
}
static?{
System.out.println("子類A的中的靜態代碼塊");
}
{
System.out.println("子類A的中的非靜態代碼塊?sya()1");
}
public?static?void?main(String[]?args)?{
A?a?=?new?A();
System.out.println("A!");
A?a2?=?new?A();
System.out.println("啟動完成");
}
}
請問控制臺打印的順序是什么?
在回答這個問題前,我們要抓住幾個關鍵的點:
①:靜態代碼塊;②:非靜態代碼塊;③構造函數;④:父類與子類;⑤:類的加載、初始化及實例化
上面這幾個就是本題的考點,我們要弄清楚每個考點在類的生命周期中執行的時機。我們來分開介紹:
先來說說的類生命周期:
此圖來源于凱哥之前寫的文章。
一:靜態代碼塊
靜態代碼塊是被static修飾的代碼塊。被static修飾的代碼塊,是屬于當前類的信息,是用來初始化類的信息。我們知道類加載過程是先將編譯后的class文件加載到內存中,一個類只會被加載到內存中一次。而static修飾的代碼塊屬于類的信息的,所以,靜態代碼塊中的代碼有且只有一次被執行。執行的時機:類被加載的時候。
二:非靜態代碼塊
非靜態代碼塊是用來初始化類實例信息的。當我們new關鍵字創建一個對象的時候,就會被執行,而且每使用一個new關鍵字創建出一個新對象的時候就會被執行一次的。非靜態代碼塊也可以叫作:非靜態初始化代碼塊的運行時機:會在構造函數執行時候,在構造函數代碼執行之前被運行的
三:構造函數
構造函數或者構造方法不用多說了吧,就是用來創建對象的。
我們來看下父類B編譯成class文件的時候,非靜態代碼塊和構造函數相關的代碼如下:
從代碼中,我們可以看出非靜態代碼塊的執行順序優先于構造函數的。
四:父類與子類
父類與子類的加載時機:父類在子類前面
需要注意的是:子類的構造方法,不管是無參構造還是有參構造,默認都會先去尋找父類的無參構造方法。如果父類中,沒有無參構造,那么子類必須使用supper這個關鍵字來調用父類帶參數的構造方法,否則在編譯期都不能通過。如下圖:
五:類的加載、初始化及實例化
類的加載、初始化及實例化過程,還有類在運行的時候,變量在JVM中怎么分布的,凱哥之前也寫過文章詳細介紹了。如果想了解更多,可以看看這幾篇文章《JVM學習第一篇思考:一個Java代碼是怎么運行起來的-上篇》、《JVM學習第二篇思考:一個Java代碼是怎么運行起來的-下篇》和《一個Java類在運行時候,變量是怎么在JVM中分布的呢?》
總結:
好了,通過上面分析,我們可以得到以下總結:
1:如果在同一個類中靜態代碼塊、非靜態代碼塊、構造函數的執行順序如下:
靜態代碼塊→非靜態代碼塊→構造函數
這個過程,我們反編譯class文件也可以看到。如下圖:
2:父類和子類中靜態代碼塊、非靜態代碼塊、構造函數的執行順序:
父類中的靜態代碼塊→子類中的靜態代碼塊→父類非靜態代碼塊→父類構造函數→子類非靜態代碼塊→子類構造函數
具體加載如下圖:
所以,根據上面的分析,我們可以知道運行的結果:
父類B的中的靜態代碼塊
子類A的中的靜態代碼塊
父類B的中的非靜態代碼塊?sya()
父類B的構造函數
子類A的中的非靜態代碼塊?sya()1
子類A的構造函數
A!
父類B的中的非靜態代碼塊?sya()
父類B的構造函數
子類A的中的非靜態代碼塊?sya()1
子類A的構造函數
啟動完成
總之一句話總結:
父類早于子類、靜態早于非靜態、非靜態早于構造函數