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();
    }
}
4

6 回答 6

5

两者都需要生成一个StackOverflowError. 当您包含此行时:

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

GenerateStackOverflow如果第一次访问该类,则会创建一个实例。

没有这一行包括:

final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");

事情很好。但这条线很关键。每次GenerateStackOverflow创建实例时,它都会尝试初始化其成员变量E2——另一个GenerateStackOverflow对象。然后实例将 E2初始化为另一个GenerateStackOverflow对象。这一直持续到 aStackOverflowError发生。

如果包含第二行但不包含第一行,则不会创建任何实例并且永远不会进入无限递归。

于 2013-08-30T22:28:17.790 回答
5

构造函数调用自身:

final GenerateStackOverflow E2 = new GenerateStackOverflow("value2");

所以,要构造一个实例,就需要构造一个实例,而实例又需要构造一个实例等等。

您的程序的 main 方法会加载该类。并且有一个静态字段调用类的构造函数,这会导致堆栈溢出。所以删除静态变量隐藏了问题,因为构造函数永远不会被调用。并且删除非静态变量会完全删除递归调用,从而修复问题。

于 2013-08-30T22:30:00.417 回答
1

static final行表示GenerateStackOverflow每次加载类时都会实例化 a ;那只是一次。该final行表示每次实例化类时都会实例化一个。

您的main方法加载类但不实例化它。所以:

  • 仅使用该static final行,加载类实例化 aGenerateStackOverflow就是这样
  • 仅使用该final行,加载类不会做任何进一步的事情
  • 使用这两种方法,加载类都会实例化 a GenerateStackOverflow(由于static行),然后实例化另一个GenerateStackOverflow(由于非static行),然后实例化另一个 GenerateStackOverflow,依此类推,直到堆栈溢出。

如果您的main方法改为:

new GenerateStackOverflow("boom");

...那么只有非线性static就足以导致溢出。

于 2013-08-30T22:29:31.087 回答
0

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

诡计

用于static final变量,以便调用构造函数,并且构造函数反过来尝试使用final变量创建自身的实例,这会导致递归调用。

于 2013-08-30T22:32:03.317 回答
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子句并new GenerateStackOverflow(...)main.

于 2013-08-30T22:37:09.473 回答
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 的行时,main 方法将运行,但永远不会实例化该类。(调用 Foo.class.getName() 不会调用构造函数。)

  • 当 T2 被移除时,构造函数将不再调用自己,并且 StackOverflowError 的源被移除。

于 2013-08-30T23:01:36.873 回答