为什么这个单例模式不会导致 StackOverflowError?
Why this Singleton Pattern doesn't result into StackOverflowError?
我最近在某处遇到过这种代码。
package com.singleton;
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
System.out.println("Done creating Singleton");
}
}
乍一看,这里面的问题可能并不明显。至少对我来说不是 :p
所以,加上这个函数,问题就清楚了
public void print() {
System.out.println("Printing inside Singleton Variable");
singleton.print();
}
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
obj.print();
System.out.println("Done creating Singleton");
}
现在,运行该程序会导致 WhosebugError
我的问题是,代码中的另一种模式中已经有这个对象。那么为什么它在第一种情况下没有导致 WhosebugError。(即在添加打印功能并在 main class 中调用它之前)
这里有两种不同的场景。当 Singleton class 本身被加载时,Singleton 的 singleton 将被初始化一次。因此,当您在单例上调用 getInstance 时,它不会重新初始化自身,而是会 return 一次又一次地使用相同的实例。
你的第二个例子是一个递归调用,它在没有任何退出条件的情况下调用自身,因此导致 WhosebugError。
您可能还会混淆递归的对象关系:
class Foo {
private Foo foo;
Foo(){ ... }
public void setFoo( Foo foo ){
this.foo = foo;
}
}
使用方法的动态递归。
类 和 Foo 一样没有问题,如果它们是在没有 运行 的情况下构造成另一种递归,例如
Foo(){
this.foo = new Foo(); // Ooops!
}
在您的第一个代码案例中,static Singleton singleton = new Singleton();
在单例 class 加载时被初始化,每次调用 Singleton.getInstance();
只是 returns 变量。
在第二种代码情况下,存在一个infinite loop call
,这将导致JVM无限期地为space分配堆栈帧,并且分配后每个堆栈大小是有限的,根据Java Virtual Machine Specification
If the computation in a thread requires a larger Java Virtual Machine stack than
is permitted, the Java Virtual Machine throws a WhosebugError.
所以这显然会导致 Whosebugerror。
你困惑的根源是你认为单例定义是递归的。实际上不是。
private static Singleton singleton = new Singleton();
这是 static 字段的定义。这意味着该字段不存在于 Singleton
的任何实例中。它与Singleton
class关联,并在class.
加载时初始化
如果这不是 static
电话,那么您是对的。创建一个实例将创建一个新字段,该字段将创建一个新实例,该实例将创建一个新字段。
但是对于静态字段,初始化仅在 class 加载时进行一次。然后它调用 class 的构造函数,仅此而已 - 不再创建 Singleton
,不再初始化,也不再自引用。
所以这个例子会导致 WhosebugError
:
public class Test
{
public Test test = new Test();
public Test() {
}
public static void main (String[] args ) {
System.out.println( new Test() );
}
}
这是因为字段 test
不是 static
而是一个实例变量,因此它的初始化是在创建新实例时完成的,并且它本身也创建了一个新实例等等上。
将 test
的定义更改为静态,错误将消失。
我最近在某处遇到过这种代码。
package com.singleton;
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
System.out.println("Done creating Singleton");
}
}
乍一看,这里面的问题可能并不明显。至少对我来说不是 :p
所以,加上这个函数,问题就清楚了
public void print() {
System.out.println("Printing inside Singleton Variable");
singleton.print();
}
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
obj.print();
System.out.println("Done creating Singleton");
}
现在,运行该程序会导致 WhosebugError
我的问题是,代码中的另一种模式中已经有这个对象。那么为什么它在第一种情况下没有导致 WhosebugError。(即在添加打印功能并在 main class 中调用它之前)
这里有两种不同的场景。当 Singleton class 本身被加载时,Singleton 的 singleton 将被初始化一次。因此,当您在单例上调用 getInstance 时,它不会重新初始化自身,而是会 return 一次又一次地使用相同的实例。
你的第二个例子是一个递归调用,它在没有任何退出条件的情况下调用自身,因此导致 WhosebugError。
您可能还会混淆递归的对象关系:
class Foo {
private Foo foo;
Foo(){ ... }
public void setFoo( Foo foo ){
this.foo = foo;
}
}
使用方法的动态递归。
类 和 Foo 一样没有问题,如果它们是在没有 运行 的情况下构造成另一种递归,例如
Foo(){
this.foo = new Foo(); // Ooops!
}
在您的第一个代码案例中,static Singleton singleton = new Singleton();
在单例 class 加载时被初始化,每次调用 Singleton.getInstance();
只是 returns 变量。
在第二种代码情况下,存在一个infinite loop call
,这将导致JVM无限期地为space分配堆栈帧,并且分配后每个堆栈大小是有限的,根据Java Virtual Machine Specification
If the computation in a thread requires a larger Java Virtual Machine stack than
is permitted, the Java Virtual Machine throws a WhosebugError.
所以这显然会导致 Whosebugerror。
你困惑的根源是你认为单例定义是递归的。实际上不是。
private static Singleton singleton = new Singleton();
这是 static 字段的定义。这意味着该字段不存在于 Singleton
的任何实例中。它与Singleton
class关联,并在class.
如果这不是 static
电话,那么您是对的。创建一个实例将创建一个新字段,该字段将创建一个新实例,该实例将创建一个新字段。
但是对于静态字段,初始化仅在 class 加载时进行一次。然后它调用 class 的构造函数,仅此而已 - 不再创建 Singleton
,不再初始化,也不再自引用。
所以这个例子会导致 WhosebugError
:
public class Test
{
public Test test = new Test();
public Test() {
}
public static void main (String[] args ) {
System.out.println( new Test() );
}
}
这是因为字段 test
不是 static
而是一个实例变量,因此它的初始化是在创建新实例时完成的,并且它本身也创建了一个新实例等等上。
将 test
的定义更改为静态,错误将消失。