Java類、例項的初始化順序
今晚是阿里巴巴 2013 校園招聘的杭州站筆試。下午匆忙看了兩張歷年試卷,去現場打了瓶醬油。
題目總體考察點偏基礎,倒數第二題(Java 附加題)比較有趣,考察了 Java 初始化機制的細節,在此摘錄出來。
題目
求如下 java 程式碼的輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
分析
程式碼主要考察的是類、變數初始化的順序。
一般的,我們很清楚類需要在被例項化之前初始化,而物件的初始化則是執行構造方法中的程式碼。
本題的程式碼顯然沒有這麼簡單了。本題中涉及到了static
{…}
和 {…}
這種形式的程式碼塊,以及在類的靜態變數中初始化該類的物件這種交錯的邏輯,容易讓人焦躁(類似於密集恐懼症吧=()。實際上,按照類的裝載、連結和初始化邏輯,以及物件初始化的順序來思考,不難得到答案。
程式碼組成
-
成員變數 2~6 行的變數是 static 的,為類 T 的靜態成員變數,需要在類載入的過程中被執行初始化;第 8 行的
int j
則為例項成員變數,只再類被例項化的過程中初始化。 -
程式碼段 9~11 行為例項化的程式碼段,在類被例項化的過程中執行;13~15 行為靜態的程式碼段,在類被載入、初始化的過程中執行。
-
方法 方法
public static int print(String str)
為靜態方法,其實現中牽涉到 k,i,n 三個靜態成員變數,實際上,這個方法是專門用來標記執行順序的方法;T 的構造方法是個例項化方法,在 T 被例項化時呼叫。 -
main 方法 main 方法中例項化了一個 T 的例項。
執行順序分析
在一個物件被使用之前,需要經歷的過程有:類的裝載 -> 連結(驗證 -> 準備 -> 解析) -> 初始化 -> 物件例項化。(詳情參見《Java 類的裝載、連結和初始化》),這裡需要注意的點主要有:
-
在類連結之後,類初始化之前,實際上類已經可以被例項化了。
就如此題程式碼中所述,在眾多靜態成員變數被初始化完成之前,已經有兩個例項的初始化了。實際上,此時對類的例項化,除了無法正常使用類的靜態承運變數以外(還沒有保證完全被初始化),JVM 中已經載入了類的記憶體結構佈局,只是沒有執行初始化的過程。比如第 3 行
public static T t1 = new T("t1");
,在連結過程中,JVM 中已經存在了一個 t1,它的值為 null,還沒有執行new T("t1")
。又比如第 5 行的public static int i = print("i");
,在沒有執行初始化時,i 的值為 0. -
先執行成員變數自身初始化,後執行
static {…}
、{…}
程式碼塊中的內容。如此策略的意義在於讓程式碼塊能處理成員變數相關的邏輯。如果不使用這種策略,而是相反先執行程式碼塊,那麼在執行程式碼塊的過程中,成員變數並沒有意義,程式碼塊的執行也是多餘。
-
類例項化的過程中,先執行隱式的構造程式碼,再執行構造方法中的程式碼 這裡隱式的構造程式碼包括了
{}
程式碼塊中的程式碼,以及例項成員變數宣告中的初始化程式碼,以及父類的對應的程式碼(還好本題中沒有考察到父類這一繼承關係,否則更復雜;))。為何不是先執行顯示的構造方法中的程式碼,再執行隱式的程式碼呢?這也很容易解釋:構造方法中可能就需要使用到例項成員變數,而這時候,我們是期待例項變數能正常使用的。
有了如上的分析,也就能推到出最終的輸出結果了。實際上,這幾個原則都不需要死記硬背,完全能通過理解整個 JVM 的執行過程來梳理出思路的。
答案
1 2 3 4 5 6 7 8 9 10 11 |
|