今天我浏览了这个网站上的一些问题,我发现一个枚举被用于单例模式,据称线程安全的好处,这种解决方案。
我从来没用过枚举,而且我已经用Java编程好几年了。显然,他们改变了很多。现在他们甚至在自己内部完全支持OOP。
为什么我应该在日常编程中使用enum ?
今天我浏览了这个网站上的一些问题,我发现一个枚举被用于单例模式,据称线程安全的好处,这种解决方案。
我从来没用过枚举,而且我已经用Java编程好几年了。显然,他们改变了很多。现在他们甚至在自己内部完全支持OOP。
为什么我应该在日常编程中使用enum ?
当前回答
除了@BradB,回答:
That is so true... It's strange that it is the only answer who mention that. When beginners discover enums, they quickly take that as a magic-trick for valid identifier checking for the compiler. And when the code is intended to be use on distributed systems, they cry... some month later. Maintain backward compatibility with enums that contains non static list of values is a real concern, and pain. This is because when you add a value to an existing enum, its type change (despite the name does not).
"Ho, wait, it may look like the same type, right? After all, they’re enums with the same name – and aren’t enums just integers under the hood?" And for these reasons, your compiler will likely not flag the use of one definition of the type itself where it was expecting the other. But in fact, they are (in most important ways) different types. Most importantly, they have different data domains – values that are acceptable given the type. By adding a value, we’ve effectively changed the type of the enum and therefore break backward compatibility.
总之:当你想使用它的时候使用它,但是,请检查所使用的数据域是一个有限的、已知的、固定的集合。
其他回答
从Java文档-
您应该在任何时候使用枚举类型 需要表示一个固定的集合 常量。包括自然enum 比如我们太阳系中的行星 你知道的系统和数据集 编译时所有可能的值 时间——例如,a上的选项 菜单、命令行标志等等。
一个常见的例子是用枚举类型替换一个类,用一组私有静态final int常量(常量数量合理)。基本上,如果你认为你在编译时知道“something”的所有可能值,你可以将其表示为枚举类型。比起带有常量的类,枚举提供了可读性和灵活性。
枚举类型几乎没有其他优点。它们总是特定枚举类的一个实例(因此出现了使用枚举作为单例的概念)。另一个优点是可以在switch-case语句中使用枚举作为类型。你也可以在枚举上使用toString()将它们打印为可读的字符串。
当一个变量(特别是一个方法参数)只能从一组可能的值中选取一个时,您应该始终使用枚举。例如类型常量(合约状态:“永久”,“临时”,“学徒”)或标志(“立即执行”,“延迟执行”)。
如果使用枚举而不是整数(或字符串代码),则增加了编译时检查,避免了传入无效常量的错误,并且记录了哪些值是可以合法使用的。
顺便说一句,过度使用枚举可能意味着你的方法做了太多的事情(通常有几个单独的方法会更好,而不是一个方法有几个标记来修改它所做的事情),但如果你必须使用标记或类型代码,枚举是最好的选择。
举个例子,哪个更好?
/** Counts number of foobangs.
* @param type Type of foobangs to count. Can be 1=green foobangs,
* 2=wrinkled foobangs, 3=sweet foobangs, 0=all types.
* @return number of foobangs of type
*/
public int countFoobangs(int type)
与
/** Types of foobangs. */
public enum FB_TYPE {
GREEN, WRINKLED, SWEET,
/** special type for all types combined */
ALL;
}
/** Counts number of foobangs.
* @param type Type of foobangs to count
* @return number of foobangs of type
*/
public int countFoobangs(FB_TYPE type)
方法调用如下:
int sweetFoobangCount = countFoobangs(3);
然后就变成:
int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);
在第二个例子中,很明显哪些类型是允许的,文档和实现不能不同步,编译器可以强制执行这一点。 另外,无效调用
int sweetFoobangCount = countFoobangs(99);
已经不可能了。
Enum表示枚举,即逐个提到(一些事情)。
enum是一种包含固定常数集的数据类型。
OR
枚举就像一个类,在编译时有一组固定的实例。
例如:
public class EnumExample {
interface SeasonInt {
String seasonDuration();
}
private enum Season implements SeasonInt {
// except the enum constants remaining code looks same as class
// enum constants are implicitly public static final we have used all caps to specify them like Constants in Java
WINTER(88, "DEC - FEB"), SPRING(92, "MAR - JUN"), SUMMER(91, "JUN - AUG"), FALL(90, "SEP - NOV");
private int days;
private String months;
Season(int days, String months) { // note: constructor is by default private
this.days = days;
this.months = months;
}
@Override
public String seasonDuration() {
return this+" -> "+this.days + "days, " + this.months+" months";
}
}
public static void main(String[] args) {
System.out.println(Season.SPRING.seasonDuration());
for (Season season : Season.values()){
System.out.println(season.seasonDuration());
}
}
}
枚举的优点:
Enum提高了编译时检查的类型安全性,以避免在运行时出现错误。 枚举可以很容易地用于交换机 枚举可以遍历 枚举可以有字段、构造函数和方法 enum可以实现许多接口,但不能扩展任何类,因为它在内部扩展了enum类
更多的
为什么要使用编程语言的特性?我们有语言的原因是
程序员以计算机可以使用的形式有效而正确地表达算法。 维护人员理解他人编写的算法并正确地进行更改。
枚举提高了正确性和可读性的可能性,而无需编写大量的样板文件。如果你愿意写样板文件,那么你可以“模拟”枚举:
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),所以这是一个值得的特性。
至少还有一个原因是值得的:
Switch语句
上面的静态最终枚举模拟没有给你的一件事是良好的开关情况。对于枚举类型,Java开关使用其变量的类型来推断枚举情况的范围,因此对于上面的枚举Color,你只需要说:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
注意它不是颜色。箱子里有红色。如果你不使用enum,使用switch的命名量的唯一方法是:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
但是现在保存颜色的变量必须是int类型。漂亮的编译器检查枚举和静态最终模拟消失了。不快乐。
折衷的方法是在模拟中使用标量值成员:
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);
}
Now:
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
}
which gives us the same thing. We can get away with this because Java enums are implemented as full classes with only a little syntactic sugar sprinkled over the top. This is again less boilerplate, but it's non-obvious unless the idiom is familiar to you. I also dislike the fact that you get the various enum functions even though they don't make much sense for the singleton: ord and values, etc. (There's actually a trickier simulation where Color extends Integer that will work with switch, but it's so tricky that it even more clearly shows why enum is a better idea.)
线程安全
只有在没有锁定的情况下惰性地创建单例时,线程安全才会成为潜在的问题。
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仍然为空,则可以创建任意数量的实例。这很糟糕。唯一的解决方案是添加同步访问来保护变量INSTANCE。
然而,上面的静态最终代码没有这个问题。它在类加载时急切地创建实例。类加载是同步的。
enum单例实际上是惰性的,因为它直到第一次使用才初始化。Java初始化也是同步的,因此多个线程不能初始化instance的多个实例。你得到了一个惰性初始化的单例,只有很少的代码。唯一的缺点是语法相当模糊。您需要了解习惯用法或彻底理解类加载和初始化是如何工作的,才能了解发生了什么。
不管别人怎么说…在我曾经工作过的一个旧项目中,实体(独立应用程序)之间的大量通信使用代表一个小集合的整数。用静态方法将set声明为enum很有用,可以从value中获取enum object,反之亦然。代码看起来更干净,开关案例可用性和更容易写入日志。
enum ProtocolType {
TCP_IP (1, "Transmission Control Protocol"),
IP (2, "Internet Protocol"),
UDP (3, "User Datagram Protocol");
public int code;
public String name;
private ProtocolType(int code, String name) {
this.code = code;
this.name = name;
}
public static ProtocolType fromInt(int code) {
switch(code) {
case 1:
return TCP_IP;
case 2:
return IP;
case 3:
return UDP;
}
// we had some exception handling for this
// as the contract for these was between 2 independent applications
// liable to change between versions (mostly adding new stuff)
// but keeping it simple here.
return null;
}
}
使用ProtocolType.fromInt(2)从接收的值(例如1,2)创建enum对象 使用myEnumObj.name写入日志
希望这能有所帮助。