为什么枚举单例是懒惰的?
Why enum singleton is lazy?
我看到了answers like these, tried to clarify via comments, and was unsatisfied by examples here。
也许是时候提出这个具体问题了...
为什么枚举单例实现被称为 lazy?
public enum EnumLazySingleton {
INSTANCE;
EnumLazySingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
它与 eager 实施有何不同?
public class BasicEagerSingleton {
private static final BasicEagerSingleton instance = new BasicEagerSingleton();
public static BasicEagerSingleton getInstance() {
return instance;
}
private BasicEagerSingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
两者都将在不访问 INSTANCE/getInstance()
的情况下初始化实例 - 例如呼叫 touchClass()
.
public class TestSingleton {
public static void main(String... args) {
System.out.println("sleeping for 5 sec...");
System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
BasicEagerSingleton.touchClass();
System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
EnumLazySingleton.touchClass();
}
}
输出:
sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE
现在,我们可以说两者都是懒惰。那么 eager 是什么?
很明显(例如)“双重检查锁定”方式实际上是懒惰的(而且混乱且缓慢)。但是如果枚举是惰性的,那么 any 单例由于不可避免的 class 加载也是惰性的——事实上,一切都是惰性的。这种区别在什么时候会失去意义?
我可以下注:
您正在尝试识别 2 个“进程”或..."things"
(让我们让它更容易理解——因为如果我开始说“代码块” , 听起来比较难)...
- 在某些时候 class-loader 会 运行,您想知道
"things"
会在 class-loader 加载一个 class.
- 在另一点调用 class 上的方法将导致另一个
"thing"
到 运行 / 执行,你想确切地知道哪个("processes"
)会开始..
以下事实是相关的:
- Static initializers are run when the class-loader loads the class. The class-loader will not load the class until the code that is
running encounters the need to load it (because a method or field has
been invoked) such as:
touchClass()
- If a singleton instance of EITHER a class, OR an enumerated type has a
field
that is being initialized in the static
part of the class it will be loaded as soon as you 'touch' the
class - because the Class-Loader runs all static initializations
for a class or enum on loading.
- Lazy loading, likely, (And this is my "interpretation" about what you are asking) would happen when a method invokation asks the class
to create a singleton instance - which could happen quite a bit of
time after the "loading" of the
class
or enum
.
A class 如下所示:
public class LazySingleton
{
// At time of class-loading, this singleton is set to 'null'
private static singleton = null;
// This is a method that will not be invoked until it is called by
// some other code-block (some other "thing")... When "touchClass()"
// is called, the singleton instance is not created.
public static LazySingleton retrieveSingleton()
{
if (singleton == null) singleton = new LazySingleton();
return singleton;
}
// DOES NOTHING... The Singleton is *not* loaded, even though the
// Class Loader has already loaded this Java ".class" file
// into memory.
public static void touchClass() { }
private LazySingleton()
{ System.out.println("constructing: LazySingleton"); }
}
另一方面:
public enum EagerEnum
{
// The class loader will run this constructor as soon as this 'enum'
// is loaded from a '.class' file (in JAR or on disk) into memory
MyEnumConstant();
private EagerEnum()
{ System.out.println("Eager Enum Constructed"); }
// This will cause the Class Loader to Load this enum from the
// Java ".class" File immediately, and the "MyEnumConstant" will
// also have to be loaded - meaning the constructor will be called.
public static void touchEnum() { }
}
所以下面的代码会产生输出
LazySingleton.touchClass(); // Prints nothing
EagerEnum.touchClass(); // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance(); // Prints "constructing: LazySingleton
前两个 linked 答案(Peter Lawrey 和 Joachim Sauer)都同意枚举是 未 延迟初始化。第三个 link 中的答案完全错误地解释了惰性初始化的含义。
将枚举用作单例的建议源自 Josh Bloch 的 Effective Java。值得注意的是,关于枚举单例的章节没有提到惰性。后面有一章专门介绍惰性初始化,同样没有提到枚举。本章包含两个亮点。
- 如果需要在静态字段上使用惰性初始化来提高性能,请使用惰性初始化持有者 class 惯用语。
- 如果您需要在实例字段上使用惰性初始化来提高性能,请使用 double-check 习惯用法。
毫无疑问,如果枚举以任何方式延迟初始化,它们将是此列表中的另一个习语。事实上它们不是,尽管如 OP 所示,对惰性初始化的含义的混淆会导致一些不正确的答案。
我看到了answers like these, tried to clarify via comments, and was unsatisfied by examples here。
也许是时候提出这个具体问题了...
为什么枚举单例实现被称为 lazy?
public enum EnumLazySingleton {
INSTANCE;
EnumLazySingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
它与 eager 实施有何不同?
public class BasicEagerSingleton {
private static final BasicEagerSingleton instance = new BasicEagerSingleton();
public static BasicEagerSingleton getInstance() {
return instance;
}
private BasicEagerSingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
两者都将在不访问 INSTANCE/getInstance()
的情况下初始化实例 - 例如呼叫 touchClass()
.
public class TestSingleton {
public static void main(String... args) {
System.out.println("sleeping for 5 sec...");
System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
BasicEagerSingleton.touchClass();
System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
EnumLazySingleton.touchClass();
}
}
输出:
sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE
现在,我们可以说两者都是懒惰。那么 eager 是什么?
很明显(例如)“双重检查锁定”方式实际上是懒惰的(而且混乱且缓慢)。但是如果枚举是惰性的,那么 any 单例由于不可避免的 class 加载也是惰性的——事实上,一切都是惰性的。这种区别在什么时候会失去意义?
我可以下注:
您正在尝试识别 2 个“进程”或..."things"
(让我们让它更容易理解——因为如果我开始说“代码块” , 听起来比较难)...
- 在某些时候 class-loader 会 运行,您想知道
"things"
会在 class-loader 加载一个 class. - 在另一点调用 class 上的方法将导致另一个
"thing"
到 运行 / 执行,你想确切地知道哪个("processes"
)会开始..
以下事实是相关的:
- Static initializers are run when the class-loader loads the class. The class-loader will not load the class until the code that is running encounters the need to load it (because a method or field has been invoked) such as:
touchClass()
- If a singleton instance of EITHER a class, OR an enumerated type has a
field
that is being initialized in thestatic
part of the class it will be loaded as soon as you 'touch' the class - because the Class-Loader runs allstatic initializations
for a class or enum on loading.- Lazy loading, likely, (And this is my "interpretation" about what you are asking) would happen when a method invokation asks the class to create a singleton instance - which could happen quite a bit of time after the "loading" of the
class
orenum
.
A class 如下所示:
public class LazySingleton
{
// At time of class-loading, this singleton is set to 'null'
private static singleton = null;
// This is a method that will not be invoked until it is called by
// some other code-block (some other "thing")... When "touchClass()"
// is called, the singleton instance is not created.
public static LazySingleton retrieveSingleton()
{
if (singleton == null) singleton = new LazySingleton();
return singleton;
}
// DOES NOTHING... The Singleton is *not* loaded, even though the
// Class Loader has already loaded this Java ".class" file
// into memory.
public static void touchClass() { }
private LazySingleton()
{ System.out.println("constructing: LazySingleton"); }
}
另一方面:
public enum EagerEnum
{
// The class loader will run this constructor as soon as this 'enum'
// is loaded from a '.class' file (in JAR or on disk) into memory
MyEnumConstant();
private EagerEnum()
{ System.out.println("Eager Enum Constructed"); }
// This will cause the Class Loader to Load this enum from the
// Java ".class" File immediately, and the "MyEnumConstant" will
// also have to be loaded - meaning the constructor will be called.
public static void touchEnum() { }
}
所以下面的代码会产生输出
LazySingleton.touchClass(); // Prints nothing
EagerEnum.touchClass(); // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance(); // Prints "constructing: LazySingleton
前两个 linked 答案(Peter Lawrey 和 Joachim Sauer)都同意枚举是 未 延迟初始化。第三个 link 中的答案完全错误地解释了惰性初始化的含义。
将枚举用作单例的建议源自 Josh Bloch 的 Effective Java。值得注意的是,关于枚举单例的章节没有提到惰性。后面有一章专门介绍惰性初始化,同样没有提到枚举。本章包含两个亮点。
- 如果需要在静态字段上使用惰性初始化来提高性能,请使用惰性初始化持有者 class 惯用语。
- 如果您需要在实例字段上使用惰性初始化来提高性能,请使用 double-check 习惯用法。
毫无疑问,如果枚举以任何方式延迟初始化,它们将是此列表中的另一个习语。事实上它们不是,尽管如 OP 所示,对惰性初始化的含义的混淆会导致一些不正确的答案。