本人菜鳥一隻,斷斷續續學習java一年有餘,之前一隻都是轉載各位大神的帖子,不敢獻醜。前幾天找工作遇到了一道筆試試題,讓我困惑許久。查了一些資料,加上一些自己的理解,終於算是大體上明白了,也下決心寫下自己的第一篇技術類博客(由於本人實在技術有限,如有錯誤,還請指正,感激不盡),當做是一個學習筆記吧,激勵自己不斷進步。
筆試試題是,給出下面代碼的輸出:
public class Test1{
public static int k=0;
public static Test1 t1=new Test1("t1");
public static Test1 t2=new Test1("t2");
public static int i=print("i");
public static int n=99;
public int j=print("j");
{
print("構造快");
}
static{
print("靜態塊");
}
public Test1(String str){
System.out.println((++k)+":"+str+" i="+i+" n="+n);
++i;++n;
}
public static int print(String str){
System.out.println((++k)+":"+str+" i="+i+" n="+n);
++n;
return ++i;
}
public static void main(String[] args) {
Test1 t=new Test1("init");
}
}
這道題當時做的是一塌糊塗。筆試結束忍不住在自己電腦上跑了一下,得到了如下結果:
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
看到結果時我就懵了,最初學習java時不重視基礎的弊端一下子顯現了出來。上網查了好多資料,java程序初始化順序解釋比較好的是:當要載入類時(注意只會載入一次),先初始化類裏的靜態域聲明變量爲默認值,(即使是賦值了,int值也要爲默認的0)然後按順序的執行靜態域和靜態塊;但是當此類有父類時,要先如果父類有靜態域或者靜態塊,先執行父類的,再執行子類的;當執行完有關靜態的東西時,就到了構造類實例時候,會先對實例域初始化爲缺省值,然後執行實例塊(用{}括起來的部分),然後執行構造方法;當此類有父類時,就應該先進入父類的構造方法執行內容,然後再回到子類構造方法裏,然後再跳出子類構造方法,執行子類裏的實例域和實例塊,然後再回到子類構造方法裏,執行子類構造方法裏面的內容.(參見http://hi.baidu.com/londalonda/item/3c2b5f722f84bc44ef1e53a7)
例如:
public class TestExtend4 {
public static void main(String[] args) {
C c = new C(); //1
}
}
class A {
static {
System.out.println("1"); //2
}
public A() { //7
System.out.println("2"); //8
}
}
class B extends A {
static {
System.out.println("a"); //3
}
public B() { //6
System.out.println("b"); //9
}
}
class C extends B {
static {
System.out.println("I"); //4
}
public C() { //5
System.out.println("II"); //11
}
{
System.out.println("yeah!"); //10
}
}
輸出結果爲:
1
a
I
2
b
yeah!
II
但是我所遇到的問題,儘管沒有父類,但是初始化情況也很複雜。下面我就說下自己的理解(其中藍色部分表示程序中的執行語句,黃色背景部分是對應的顯示結果部分):
1、該程序加載完成後,在執行main函數前,先要初始化Test1類裏面的靜態變量,此時會初始化靜態變量爲默認值,即有:k=0,t1=null,t2=null,i=0,n=0。之後按照順序開始執行靜態變量的賦值操作有k=0。
2、接下來執行t1=new Test1("t1")。執行該條程序時,需要執行Test1類的構造函數。而按照類成員初始化的順序,類內部的變量和實例塊會在構造器被調用之前得到初始化。因此就需要按照先後順序先初始化類變量j,再執行構造塊,最後執行構造函數。執行public int j=print("j");由於print方法被重寫,所以得到輸出結果1:j i=0 n=0(此處的i=0和n=0正好證明了靜態變量在加載時只是完成了初始化,按照順序還沒有進行賦值),此時變爲i=1,n=1。接着執行構造塊部分,得到輸出結果2:構造快 i=1 n=1,此時變爲i=2,n=2。最後執行Test1的構造函數,得到輸出結果3:t1 i=2 n=2,此時變爲i=3,n=3。
3、現在執行t2=new Test1("t2")。執行該條程序時,步驟同上一步,在構造函數調用之前,再次調用類內部的變量和實例塊。因而陸續執行了public int j=print("j");和實例塊部分。得到輸出4:j i=3 n=3,此時變爲i=4 n=4。5:構造快 i=4 n=4,此時變爲i=5 n=5。6:t2 i=5 n=5,此時變爲i=6 n=6。此處,特別注意,每次調用類的構造方法之前,該類的內部變量和實例塊都會得到調用,驗證如下:
public class Test5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ObjA a1=new ObjA();
ObjA a2=new ObjA();
ObjA a3=new ObjA();
}
}
class ObjA{
{
System.out.println("ObjA中實例塊被執行");
}
static {
System.out.println("ObjA中靜態塊被執行");
}
public ObjA(){
System.out.println("constructor in ObjA");
}
}
得到輸出結果爲:
ObjA中靜態塊被執行
ObjA中實例塊被執行
constructor in ObjA
ObjA中實例塊被執行
constructor in ObjA
ObjA中實例塊被執行
constructor in ObjA
4、下面執行i=print("i")。得到輸出7:i i=6 n=6,此時變爲i=7 n=7。
5、下面執行n=99。此時變爲i=7 n=99。
6、接着,執行靜態塊static{ print("靜態塊"); }。在執行main()函數前,還有執行靜態塊部分,因而輸出8:靜態塊 i=7 n=99。然後變爲i=8 n=100。
7、現在開始執行main()函數。通過1~6步,初始化完成,執行main函數。執行Test1 t=new Test1("init");按照之前所說,類的構造方法調用之前,還要先調用類內部變量和實例塊。因而順序執行public int j=print("j");實例塊代碼和構造函數。得到結果9:j i=8 n=100。然後有i=9 n=101。運行實例塊得到結果10:構造快 i=9 n=101。同時又i=10 n=102。運行構造函數得到結果11:init i=10 n=102。
至此程序完畢。從上不難發現,在每次構造函數運行前,類的實例變量和實例塊部分都會得到執行,且執行順序服從先後順序。