TL;DR:不要做我所做的事情。运行并阅读下面最小重现器中的代码注释。
我有一个
enum
,它采用 class
的实例,它本身是一个从 enum
获取值的单例。仅仅阅读该内容就表明这是一个糟糕的设计。
下面我们可以看到一些不一致的结果;例如,在调用
MyEnum.VALUE.myClassInstance
之前打印 MyClass.getInstance()
会导致 MyEnum.VALUE.myClassInstance
为非 null
,但在打印 MyClass.getInstance()
之前调用 MyEnum.VALUE.myClassInstance
会导致 MyEnum.VALUE.myClassInstance
为空。
但是为什么??
public class StrangeEnumFailure {
public static void main(final String[] args) {
// Enabling any of the following three lines will cause the prints to print the expected values
// MyEnum.VALUE.getMyClassInstance();
// MyEnum.VALUE.getClass();
// System.out.println(MyEnum.VALUE.myClassInstance); // Not null!
System.out.println(MyClass.getInstance()); // Not null
System.out.println(MyEnum.VALUE.myClassInstance); // null... but only if we do it after MyClass.getInstance()!
System.out.println(MyClass.INSTANCE); // Not null
System.out.println(MyEnum.VALUE.getMyClassInstance()); // null
System.out.println(MyEnum.VALUE.myClassInstance); // null
}
public static class MyClass {
static final MyClass INSTANCE = new MyClass(MyEnum.VALUE);
final MyEnum enumValue;
private MyClass(final MyEnum enumValue) {
this.enumValue = enumValue;
}
static MyClass getInstance() {
return INSTANCE;
}
}
public enum MyEnum {
VALUE(MyClass.INSTANCE);
final MyClass myClassInstance;
MyEnum(final MyClass myClassInstance) {
this.myClassInstance = myClassInstance;
}
MyClass getMyClassInstance() {
return this.myClassInstance;
}
}
}
(不要评判代码,非私有字段和缺少 JavaDoc 只是为了方便演示)
Java版本信息:
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7)
OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)
该构造导致类加载依赖于顺序(总体)。
如果你要求 Java 首先使用
MyClass
:
MyClass
INSTANCE
MyEnum
MyClass.INSTANCE
MyClass.INSTANCE == null
,因为它还没有设置,因为我们实际上正在设置它,所以this.myClassInstance = null
this.enumValue = MyEnum.VALUE
因此,在此加载序列结束时,
INSTANCE
不是null
,但当我们在实例化null
值时抓取它时,是
enum
。
另一方面,如果你要求 Java 首先使用
MyEnum
:
MyEnum
MyClass.INSTANCE
MyClass
INSTANCE
MyEnum.VALUE
,我们当前正在实例化this.myEnumValue = MyEnum.VALUE
INSTANCE
现在是非null
this.myClassInstance = MyClass.INSTANCE
where MyClass.INSTANCE != null
因为我们只是在加载时构造了它 MyClass
因此,一切都按预期设置。
我无意中这样做了,并且将恢复它,因为这显然是一个糟糕的设计选择。
别这样做。
通常,在 Java 中,不需要 单例模式。
尽管经过一些研究,似乎应该在类上使用 enum。
考虑到它本质上是单个实例。
enum MyClass {
instance;
String s;
static MyClass getInstance() {
return instance;
}
String getS() {
return s;
}
void setS(String s) {
this.s = s;
}
@Override
public String toString() {
return Objects.toIdentityString(this);
}
}
enum MyEnum {
VALUE(MyClass.instance);
final MyClass myClassInstance;
MyEnum(final MyClass myClassInstance) {
this.myClassInstance = myClassInstance;
}
MyClass getMyClassInstance() {
return this.myClassInstance;
}
}