问题描述
我看到answers like these,试图通过comments进行澄清,但对examples here不满意。
也许是时候回答这个特定问题了...
为什么枚举单例实现被称为惰性?
public enum EnumLazySingleton {
INSTANCE;
EnumLazySingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
与渴望实施有何不同?
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
现在,我们可以说两者都是惰性。那么渴望是什么?
很明显,“双重检查锁定”方式实际上是懒惰的(又杂乱又缓慢)。但是,如果枚举是惰性的,则由于不可避免的类加载,任何单例都是惰性的-实际上,所有事物都是惰性的。在什么时候这种区别将不再有意义?
解决方法
前两个链接的答案(由 Peter Lawrey 和 Joachim Sauer )都同意枚举不是未初始化的。关于延迟初始化的含义,第三个链接中的答案完全是错误的。
使用枚举作为单例的建议源自Josh Bloch的Effective Java。值得注意的是,有关枚举单例的这一章没有提及懒惰。后面的章节专门介绍延迟初始化,同样也没有提及枚举。本章包含两个重点。
- 如果您需要使用惰性初始化来提高静态字段的性能,请使用惰性初始化持有人类习惯用法。
- 如果您需要使用延迟初始化来提高实例字段的性能,请使用仔细检查的习惯用法。
毫无疑问,如果枚举以任何方式延迟初始化,则枚举将是此列表中的另一个惯用法。实际上,事实并非如此,尽管如OP所示,对延迟初始化含义的混淆会导致一些错误的答案。
,我可以下注吗?
您正在尝试识别2个“进程”或... "things"
(让我们易于理解-因为如果我开始说“代码块”,听起来会更困难)...
- 在某个时候类加载器将运行,并且您想知道在类加载器加载类时将执行什么
"things"
。 - 另一方面,在类上调用方法将导致另一个
"thing"
运行/执行,您想确切地知道哪个(哪个 {{1 }} )将开始。
以下事实是相关的:
- 静态初始化程序在类加载器加载类时运行。该类加载器将不会加载该类,直到 运行时需要加载它(因为方法或字段具有 被调用),例如:
"processes"
- 如果 EITHER 类的单例实例,则 OR 枚举类型具有
touchClass()
,该实例将在field
该类的一部分,只要您“触摸” class-,因为类加载器会在加载时为类或枚举运行所有static
。- 当方法调用询问类时,可能会发生延迟加载(这是我对您所要询问的“解释”) 创建一个单例实例-这可能会发生很多
static initializations
或class
“加载”之后的时间。
如下所示的类:
enum
另一方面:
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() { }
}