Java類、實例初始化的順序
求如下 java 代碼的輸出??
class T implements Cloneable{ public static int k = 0; public static T t1 = new T("t1"); public static T t2 = new T("t2"); public static int i = print("i"); public static int n = 99; public int j = print("j"); { print("構造快"); } static { print("靜態塊"); }public T(String str) { System.out.println((++k) + ":" + str + " i=" + i + " n=" + n); ++n; ++ i; } public static int print(String str){ System.out.println((++k) +":" + str + " i=" + i + " n=" + n); ++n; return ++ i; } public static void main(String[] args){ T t= new T("init"); } }
分析:
代碼主要考察類、變量初始化的順序。
一般的,我們很清楚類需要在被實例化之前初始化,而對象的初始化則是運行構造方法中的代碼。
代碼組成:
-
成員變量 2~6 行的變量是 static 的,為類 T 的靜態成員變量,需要在類加載的過程中被執行初始化;第 8 行的
int j
則為實例成員變量,只再類被實例化的過程中初始化。 -
代碼段 9~11 行為實例化的代碼段,在類被實例化的過程中執行;13~15 行為靜態的代碼段,在類被加載、初始化的過程中執行。
-
方法 方法
public static int print(String str)
-
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,同理 n 在初始化前值也為 0.
- 類實例化的過程中,先執行父類的構造器,然後執行隱式的構造代碼,再執行構造方法中的代碼。
實際上,在編譯 Java 代碼到字節碼的過程中,編譯器已經將源碼中實例化相關的代碼集中到了構造方法中。
這裏隱式的構造代碼包括了{}
代碼塊中的代碼,以及實例成員變量聲明中的初始化代碼。它們的執行順序以源代碼中代碼出現的順序為準。為何不是先執行顯示的構造方法中的代碼,再執行隱式的代碼呢?這也很容易解釋:構造方法中可能就需要使用到實例成員變量,而這時候,我們是期待實例變量能正常使用的。
有了如上的分析,也就能推到出最終的輸出結果了。實際上,這幾個原則都不需要死記硬背,完全能通過理解整個 JVM 的執行過程來梳理出思路的。
答案:
1:j i=0 n=0 2:構造快 i=1 n=1 3:t1 i=2 n=2 4:j i=3 n=3 5:構造快 i=4 n=4 6:t2 i=5 n=5 7:i i=6 n=6 8:靜態塊 i=7 n=99 9:j i=8 n=100 10:構造快 i=9 n=101 11:init i=10 n=102
轉自:http://biaobiaoqi.github.io/blog/2013/09/22/java-initialization/
Java類、實例初始化的順序