这些是 、 和 的主要论据enum,EnumMap并EnumSet通过简短的例子。
案例为enum
从 Java 6 开始,java.util.Calendar这是一个杂乱无章的类的示例,它可以从使用中受益匪浅enum(以及其他改进)。
当前Calendar定义了以下常量(除其他外):
// int constant antipattern from java.util.Calendar
public static final int JANUARY = 0;
public static final int FEBRUARY = 1;
...
public static final int SUNDAY = 1;
public static final int MONDAY = 2;
...
这些都是int,即使它们显然代表不同的概念实体。
以下是一些严重的后果:
它很脆;必须注意在需要时分配不同的编号。
如果我们错误地设置了MONDAY = 0;, SUNDAY = 0;,那么我们有MONDAY == SUNDAY
没有命名空间也没有类型安全,因为一切都只是int:
我们可以setMonth(JANUARY),但我们也可以setMonth(THURSDAY)或setMonth(42)
谁知道set(int,int,int,int,int,int)(真正的方法!)做了什么!
相比之下,我们可以有这样的东西:
// Hypothetical enums for a Calendar library
enum Month {
JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
SUNDAY, MONDAY, ...
}
现在我们再也不用担心MONDAY == SUNDAY(它永远不会发生!),而且因为Month和DayOfWeek是不同的类型,setMonth(MONDAY)所以不会编译。
此外,这里有一些前后代码:
// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
...
}
在这里,我们进行了各种假设,例如JANUARY + 1 == FEBRUARY等。另一方面,enum对应物更简洁、更具可读性,并且做出的假设更少(因此出现错误的机会也更少):
// AFTER with enum
for (Month month : Month.values()) {
...
}
实例字段的情况
在 Java 中,enum它class具有许多特殊属性,但class仍然允许您在必要时定义实例方法和字段。
考虑以下示例:
// BEFORE: with int constants
public static final int NORTH = 0;
public static final int EAST = 1;
public static final int SOUTH = 2;
public static final int WEST = 3;
public static int degreeFor(int direction) {
return direction * 90; // quite an assumption!
// must be kept in-sync with the int constants!
}
//...
for (int dir = NORTH; dir <= WEST; dir++) {
... degreeFor(dir) ...
}
另一方面,enum你可以这样写:
enum Direction {
NORTH(0), EAST(90), SOUTH(180), WEST(270);
// so obvious! so easy to read! so easy to write! so easy to maintain!
private final int degree;
Direction(int degree) { this.degree = degree; }
public int getDegree() { return degree; }
}
//...
for (Direction dir : Direction.values()) {
... dir.getDegree() ...
}
实例方法的案例
考虑以下示例:
static int apply(int op1, int op2, int operator) {
switch (operator) {
case PLUS : return op1 + op2;
case MINUS : return op1 - op2;
case ...
default: throw new IllegalArgumentException("Unknown operator!");
}
}
如前面的示例所示,enum在 Java 中可以有实例方法,但不仅如此,每个常量也可以有自己的特定@Override方法。这显示在以下代码中:
enum Operator {
PLUS { int apply(int op1, int op2) { return op1 + op2; } },
MINUS { int apply(int op1, int op2) { return op1 - op2; } },
...
;
abstract int apply(int op1, int op2);
}
案例为EnumMap
这是来自Effective Java 2nd Edition的引述:
enum永远不要从其ordinal();派生与 an 关联的值。而是将其存储在实例字段中。(第 31 条:使用实例字段而不是序数)使用序数来索引数组很少合适:使用EnumMap代替。一般原则是应用程序程序员应该很少(如果有的话)使用Enum.ordinal. (第 33 项:使用EnumMap代替序数索引)
基本上和以前一样,你可能会有这样的事情:
// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...
employeeOfTheMonth[JANUARY] = jamesBond;
现在你可以拥有:
// AFTER, with enum and EnumMap
Map
employeeOfTheMonth.put(Month.JANUARY, jamesBond);
案例为EnumSet
int例如在 C++ 中经常使用两个常数的幂来表示位集。这依赖于按位运算。一个示例可能如下所示:
public static final int BUTTON_A = 1;
public static final int BUTTON_B = 2;
public static final int BUTTON_X = 4;
public static final int BUTTON_Y = 8;
int buttonState = BUTTON_A | BUTTON_X; // A & X are pressed!
if ((buttonState & BUTTON_B) != 0) { // B is pressed...
...
}
使用enumand EnumSet,这看起来像这样:
enum Button {
A, B, X, Y;
}
Set
if (buttonState.contains(Button.B)) { // B is pressed...
...
}
参考
Java Language Guide/Enums——非常完整的处理,有很多例子
也可以看看
有效的 Java 第 2 版
第 30 条:使用enum代替int常量
第 31 条:使用实例字段而不是序数
第 32 项:使用EnumSet而不是位域
第 33 条:使用EnumMap而不是序数索引
相关问题
Java中的枚举。好处?
是否有类似 C 的方式从 java 中的枚举中获取项目编号?——是的ordinal(),但是……
使用 Enum 的序数值来索引 Java 中的数组是不好的做法吗?——很可能,是的!
[java] 每个枚举项的编号?——主要候选人EnumSet!