[英]Why does this Java code create a stack overflow error?
執行時,下面的代碼會產生堆棧溢出錯誤。 但是,如果刪除其中之一
static final GenerateStackOverflow E1 = new GenerateStackOverflow("value1");
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");
它在沒有堆棧溢出錯誤的情況下運行。 如果我有上面的兩行,怎么會出現堆棧溢出錯誤,但如果只有一行是在類中,則沒有錯誤?
public class GenerateStackOverflow {
private final String value;
static final GenerateStackOverflow E1 = new GenerateStackOverflow("value1");
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");
public GenerateStackOverflow(String value) {
System.out.println("GenerateStackOverflow.GenerateStackOverflow()");
this.value = value;
}
public String getValue() {
return value;
}
public static void main(String[] args) {
GenerateStackOverflow.class.getName();
}
}
兩者都需要生成StackOverflowError
。 當您包含此行時:
static final GenerateStackOverflow E1 = new GenerateStackOverflow("value1");
如果首次訪問該類,則會創建GenerateStackOverflow
的實例。
沒有這一行包括:
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");
事情很好。 但這條線很重要。 每次創建GenerateStackOverflow
實例時,它都會嘗試初始化其成員變量E2
- 另一個GenerateStackOverflow
對象。 然后該實例將其 E2
初始化為另一個GenerateStackOverflow
對象。 這將一直持續到發生StackOverflowError
。
如果包含第二行但第一行不包含,則不會創建任何實例,並且永遠不會輸入無限遞歸。
構造函數調用自身:
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");
因此,要構造一個實例,需要構造一個需要構造實例的實例,等等。
你的程序的main方法加載了這個類。 並且有一個靜態字段調用類的構造函數,這會創建堆棧溢出。 因此,刪除靜態變量會隱藏問題,因為永遠不會調用構造函數。 刪除非靜態變量會完全刪除遞歸調用,從而修復了probem。
static final
行意味着每次加載類時都會實例化GenerateStackOverflow
; 這只是一次。 final
一行表示每次實例化類時都會實例化一行。
您的main
方法加載類但不實例化它。 所以:
static final
行,加載類將實例化GenerateStackOverflow
,就是這樣 final
一行,加載類不會做任何進一步的操作 GenerateStackOverflow
(由於static
行),然后實例化另一個GenerateStackOverflow
(由於非static
行),然后實例化另一個 GenerateStackOverflow
,依此類推,直到你得到堆棧溢出。 如果你的main
方法是:
new GenerateStackOverflow("boom");
...然后只是非static
線就足以引起溢出。
遞歸構造函數調用是一個簡單的答案。
對於變量使用static final
,以便調用構造函數並且構造函數inturn嘗試使用final
變量創建自身的實例,這將導致Recursive調用。
這會產生無任何字段的無限循環:
public class GenerateStackOverflow {
private final String value;
static {
GenerateStackOverflow E1 = new GenerateStackOverflow("value1");
}
public GenerateStackOverflow(String value) {
System.out.println("GenerateStackOverflow.GenerateStackOverflow()");
GenerateStackOverflow E2 = new GenerateStackOverflow("value2");
this.value = value;
}
public String getValue() {
return value;
}
public static void main(String[] args) {
GenerateStackOverflow.class.getName();
}
}
這不是產生循環的最終字段,而是new
操作。
您還可以通過刪除static
子句並在main
插入new GenerateStackOverflow(...)
調用來獲取錯誤。
您的代碼示例雖然仍然顯示類似的行為,但可以簡化為:
public class Foo {
static Foo T1 = new Foo();
Foo T2 = new Foo();
Foo() {
}
public static void main(String[] args) {
}
}
在創建實例時將分配T2,這意味着代碼可以重構為:
public class Foo {
static Foo T1 = new Foo();
Foo T2;
Foo() {
T2 = new Foo();
}
public static void main(String[] args) {
}
}
第二種形式使構造函數調用自身非常明顯。 那么為什么刪除T1或T2也會刪除StackOverflowError?
刪除帶有T1的行時,將運行main方法,但該類從未實例化。 (調用Foo.class.getName()不會調用構造函數。)
刪除T2后,構造函數將不再調用自身,並刪除StackOverflowError的源代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.