这些是enum、EnumMap 和EnumSet 的主要参数(通过简短的示例)。
enum的情况
从 Java 6 开始,java.util.Calendar 是一个杂乱无章的类示例,使用 enum(以及其他改进)可能会受益良多。
目前Calendar 定义了以下常量(among many others):
// 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:
相比之下,我们可以有这样的东西:
// 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!");
}
}
如上例所示,Java 中的enum 可以有实例方法,但不仅如此,每个常量也可以有自己特定的@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的引述:
永远不要从ordinal() 派生与enum 关联的值;而是将其存储在实例字段中。 (第 31 条:使用实例字段而不是序数)使用序数来索引数组很少合适:使用 EnumMap 代替。一般原则是应用程序程序员应该很少(如果有的话)使用Enum.ordinal。 (第 33 项:使用EnumMap 代替序号索引)
基本上和以前一样,您可能会遇到这样的情况:
// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...
employeeOfTheMonth[JANUARY] = jamesBond;
现在你可以拥有:
// AFTER, with enum and EnumMap
Map<Month, Employee> employeeOfTheMonth = ...
employeeOfTheMonth.put(Month.JANUARY, jamesBond);
EnumSet的情况
经常使用两个int 常量的幂,例如在 C++ 中表示位集。这依赖于bitwise operations。示例可能如下所示:
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...
...
}
使用enum 和EnumSet,可能看起来像这样:
enum Button {
A, B, X, Y;
}
Set<Button> buttonState = EnumSet.of(Button.A, Button.X); // A & X are pressed!
if (buttonState.contains(Button.B)) { // B is pressed...
...
}
参考文献
另见
-
有效的 Java 第 2 版
- 第 30 项:使用
enum 代替 int 常量
- 第 31 项:使用实例字段而不是序数
- 第 32 项:使用
EnumSet 代替位字段
- 第 33 项:使用
EnumMap 代替序号索引
相关问题