今天我浏览了这个网站上的一些问题,我发现了一个 enum
被用于单例模式,关于这种解决方案声称的线程安全优势。
我从未使用过 enum
s 并且我已经使用 Java 编程多年了。显然,他们改变了很多。现在他们甚至在自己内部全面支持 OOP。
现在为什么以及我应该在日常编程中使用枚举?
原文由 MatBanik 发布,翻译遵循 CC BY-SA 4.0 许可协议
为什么要使用任何编程语言功能?我们拥有语言的原因是
枚举提高了正确性和可读性的可能性,而无需编写大量样板。如果您愿意编写样板文件,那么您可以“模拟”枚举:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
现在你可以写:
Color trafficLightColor = Color.RED;
上面的样板与
public enum Color { RED, AMBER, GREEN };
两者都提供来自编译器的相同级别的检查帮助。样板只是更多的打字。但是节省大量的输入可以让程序员更有 _效率_(见 1),所以这是一个有价值的特性。
至少还有一个原因是值得的:
开关语句
上面的 static final
枚举模拟 没有 给你的一件事很好 switch
案例。对于枚举类型,Java 开关使用其变量的类型来推断枚举情况的范围,因此对于上面的 enum Color
你只需要说:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
请注意,在案例中它不是 Color.RED
。如果您不使用枚举,则将命名数量与 switch
一起使用的唯一方法是:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
但是现在保存颜色的变量必须具有类型 int
。枚举和 static final
模拟的良好编译器检查消失了。不开心。
折衷方案是在模拟中使用标量值成员:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
现在:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
但请注意,还有更多样板文件!
使用枚举作为单例
从上面的样板文件中,您可以看出为什么枚举提供了一种实现单例的方法。而不是写:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
然后访问它
SingletonClass.INSTANCE
我们只能说
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
这给了我们同样的东西。我们可以避开这个问题,因为 Java 枚举 是 作为完整的类实现的,只是在顶部撒了一点语法糖。这又是一个较少的样板文件,但除非您熟悉这个成语,否则它并不明显。我也不喜欢你得到各种枚举函数,即使它们对单例没有多大意义: ord
和 values
等(实际上有一个更棘手的模拟 Color extends Integer
可以与 switch 一起使用,但它非常棘手,它甚至更清楚地说明了为什么 enum
是一个更好的主意。)
线程安全
只有在没有锁定的情况下懒惰地创建单例时,线程安全才是一个潜在的问题。
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
如果许多线程同时调用 getInstance
而 INSTANCE
仍然为空,则可以创建任意数量的实例。这是不好的。唯一的解决方案是添加 synchronized
访问保护变量 INSTANCE
。
但是上面的 static final
代码没有这个问题。它在类加载时急切地创建实例。类加载是同步的。
enum
单例实际上是惰性的,因为它直到第一次使用才被初始化。 Java 初始化也是同步的,因此多个线程不能初始化 INSTANCE
的多个实例。您将获得一个代码很少的惰性初始化单例。唯一的缺点是相当晦涩的语法。您需要知道这个惯用语或彻底了解类加载和初始化的工作原理才能知道发生了什么。
原文由 Gene 发布,翻译遵循 CC BY-SA 3.0 许可协议
15 回答8.4k 阅读
8 回答6.2k 阅读
1 回答4.1k 阅读✓ 已解决
3 回答2.2k 阅读✓ 已解决
2 回答3.1k 阅读
2 回答3.8k 阅读
3 回答1.7k 阅读✓ 已解决
当变量(尤其是方法参数)只能从一小组可能值中取一个时,您应该始终使用枚举。例如类型常量(合同状态:“永久”、“临时”、“学徒”)或标志(“立即执行”、“延迟执行”)。
如果您使用枚举而不是整数(或字符串代码),您会增加编译时检查并避免因传递无效常量而导致的错误,并且您会记录哪些值可以合法使用。
顺便说一句,过度使用枚举可能意味着你的方法做的太多(最好有几个单独的方法,而不是一个方法采用多个标志来修改它的作用),但是如果你必须使用标志或类型代码,枚举是要走的路。
举个例子,哪个更好?
相对
方法调用如下:
然后变成:
在第二个示例中,可以立即清楚哪些类型是允许的,文档和实现不能不同步,并且编译器可以强制执行此操作。另外,无效的调用如
不再可能。