为什么这段Java代码会导致堆栈溢出错误?

3
以下代码在执行时会产生堆栈溢出错误。但是,如果移除其中一个行,则不会出现堆栈溢出错误。
  • 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();
    }
}

1
因为每个对象类的实例都是该类的新实例。这将创建该类的新实例,进而又创建该类的新实例... - Hot Licks
你正在创建一个无限长的实例链表(通过 E2 引用)。 - aioobe
当对象实例化时,它会实例化自己... - t777
你只有在两行代码同时存在时才会出现错误,因为没有第一行代码创建类的实例,也没有第二行代码递归调用。 - Hot Licks
3
不确定为什么这个回答被踩了。有趣的例子。 - Kevin Bowersox
@KevinBowersox - 我不明白你为什么认为这很“有趣”——这只是简单的无限递归,没有别的。 - Hot Licks
6个回答

5

两者均需要才能生成StackOverflowError。 当您包含此行代码时:

static final GenerateStackOverflow E1 = new GenerateStackOverflow("value1");

当第一次访问类时,将创建GenerateStackOverflow的实例。
如果不包含此行:
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");

事情还好,但是这一行很关键。每次创建一个GenerateStackOverflow实例,它都会尝试初始化其成员变量E2 ——另一个GenerateStackOverflow对象。然后,那个实例的E2被初始化为另一个GenerateStackOverflow对象。这种情况继续发生,直到出现StackOverflowError
如果只包括第二行而不包括第一行,则不会创建任何实例,也不会进入无限递归。

5
构造函数调用自身:
final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");

因此,要构造一个实例,您需要构造一个实例,该实例需要构造另一个实例,以此类推。

您程序的主方法加载了该类。并且有一个静态字段调用类的构造函数,从而创建堆栈溢出。因此,删除静态变量会隐藏问题,因为构造函数从未被调用。删除非静态变量完全删除了递归调用,从而解决了问题。


1
"

static final这一行表示每次加载类时都会实例化一个GenerateStackOverflow,也就是只有一次。而final这一行表示每次实例化类时都会实例化一个。

你的main方法加载了类但没有实例化它。所以:

  • 仅使用static final这一行,加载类将实例化一个GenerateStackOverflow,然后就完成了。
  • 仅使用final这一行,加载类不会做进一步的操作。
  • 两个都有时,加载类将实例化一个GenerateStackOverflow(由于static行),然后实例化另一个GenerateStackOverflow(由于非static行),然后实例化另一个GenerateStackOverflow,以此类推,直到出现堆栈溢出错误。

如果你的main方法改为:

"
new GenerateStackOverflow("boom");

如果只有非static的那一行,就足以引起溢出。


0

这将产生没有任何字段的无限循环:

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(...)调用来获取错误。


0

您的代码示例可以通过以下方式进行简化,同时仍然展现出类似的行为:

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的行时,主方法将运行但是类不会被实例化。(调用Foo.class.getName()不会调用构造函数。)

  • 当删除T2时,构造函数将不再调用自身,StackOverflowError的来源也被移除。


0

递归构造函数调用是简单的答案。

技巧

使用static final变量,以便调用构造函数,而该构造函数反过来尝试使用final变量创建自身的实例,从而导致递归调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接