一道阿里巴巴筆試題中,關於java中的變量,初始化快,構造函數初始化分析

     本人菜鳥一隻,斷斷續續學習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

      至此程序完畢。從上不難發現,在每次構造函數運行前,類的實例變量和實例塊部分都會得到執行,且執行順序服從先後順序。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章