今天,在一個羣裏面有網友問到這樣一個問題,以下代碼被調用運行時爲何會造成棧溢出(StackOverflowError)的錯誤:
public class Constructor { Constructor c = new Constructor(); public static void main(String[] args) { Constructor test = new Constructor(); } }
一般人,初看感覺沒啥問題,但是自己在機器上跑了一下,就會爆出這樣的錯誤,如圖
從這些錯誤中我們可以得到這樣一個信息:程序運行時候,Constructor實例初始化方法
(在這裏就是<init>,這個後面還會細講),被瘋狂的調用。
羣裏面的人,對這個問題的回答,其中有個網友通過現象推結果,說是:“在這個Constuctor類中,由於類的成員c本身就是
Constructor類型的,所以當類的成員初始化時,類的構造函數就被遞歸調用了”。
這個回答,說實話挺沒邏輯的,看的我比較雲裏霧裏。其實這個問題如果我們從反彙編後的該類的字節碼入手,
就能很清楚的得到問題的答案了.
我們用java –p Constructor 得到反彙編後的字節碼,如下:
Constructor c;
public Constructor();
Code:
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: aload_0
5: new #1; //class Constructor
8: dup
9: invokespecial #12; //Method "<init>":()V
12: putfield #13; //Field c:LConstructor;
15: return
public static void main(java.lang.String[]);
Code:
0: new #1; //class Constructor
3: dup
4: invokespecial #12; //Method "<init>":()V
7: astore_1
8: return
}
我們只要關注此類的構造方法Constructor中的代碼就行了,我們可以發現在這構造方法裏面,
出現了new #1;//class Constructor 這樣的語句,他表示創建一個Constructor類型的對象。
從這裏面我們便可以明白:
即便你在構造函數外面,顯式的初始化了一個成員如c,但是類編譯後運行時,
這種顯式初始化成員的真正初始化還是放在構造函數中,統一進行的。
所以像剛纔的那種代碼,相當於就是在Constructor構造函數裏面調用了自身.就像下面代碼一樣:
Constructor c;
public Constructor() {
c = new Constructor();
}
public static void main(String[] args) {
Constructor test = new Constructor();
}
}
PS:順便補充一下,幾條Bytecode指令的意思: new 創建一個新對象.
invokespecial 根據編譯時類型來調用實例方法.
invokevirtual 根據運行時對象實際類型,來調用實例方法.
putfield 設置對象中字段的值.