我是一名即将毕业的计算机科学专业学生,在我的编程生涯中,我很少看到使用枚举的情况,除非是用于代表标准扑克牌的面值等经典案例。
你知道有哪些巧妙的方法可以在日常编码中使用枚举吗?
枚举为什么如此重要,在什么情况下应该确定构建枚举是最佳方法?
我是一名即将毕业的计算机科学专业学生,在我的编程生涯中,我很少看到使用枚举的情况,除非是用于代表标准扑克牌的面值等经典案例。
你知道有哪些巧妙的方法可以在日常编码中使用枚举吗?
枚举为什么如此重要,在什么情况下应该确定构建枚举是最佳方法?
这些是针对 enum
、EnumMap
和 EnumSet
的主要论据,通过简短的示例进行说明。
enum
辩护截至Java 6,在使用 enum
(以及其他改进)之后,java.util.Calendar
是混乱类的一个例子,可以从中受益匪浅。
当前,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!");
}
}
如前面的例子所示,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()
派生一个与其关联的值; 而是将它存储在实例字段中。(第31条:使用实例字段代替序数) 几乎不可能适当地使用 ordinal 来索引数组: 使用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
在C++中,我们经常使用2的幂次方作为int
常量来表示位集合,这依赖于按位运算。一个例子可能如下所示:
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...
...
}
ordinal()
,但是...EnumSet
!case MINUS: return op1 - op2;
(之前是加号操作符)。 - danyimString
/int
常量的内容现在应该定义为枚举类型。例如,不要再使用以下方式:public class StatusConstants {
public int ACTIVE = 1;
public int SUSPENDED = 2;
public int TEMPORARY_INACTIVE = 3;
public int NOT_CONFIRMED = 4;
}
现在您拥有更安全和更开发者友好的:
public enum Status {
ACTIVE, SUSPENDED, TEMPORARY_INACTIVE, NOT_CONFIRMED
}
考虑一些简单的代码:
没有枚举:
int OPT_A_ON = 1;
int OPT_A_OFF = 0;
int OPT_B_ON = 1;
int OPT_B_OFF = 0;
SetOption(OPT_A_ON, OPT_B_OFF); // Declaration: SetOption(int, int)
看起来不错,对吧?但是SetOptions()函数需要先传入选项B再传入选项A。虽然这段代码可以通过编译,但运行时会将选项设置反了。
现在,使用枚举类型:
enum OptA {On =1, Off = 0};
enum OptB {On =1, Off = 0};
SetOption(OptA.On, OptB.Off);// Declaration: SetOption(OptB, OptA)
现在我们犯了同样的错误,但这一次,由于枚举是不同类型,所以编译器会捕捉到这个错误。
(注意:我并不是真正的Java程序员,所以请原谅小的语法错误)
enum
值是完整的对象,而不仅仅是整数的花哨快捷方式,因此你不能像这样简单地定义它们的整数值。只需删除该赋值语句,你的代码就会正确。 - Joachim Sauer我认为你说的是枚举,而不是枚举器。
枚举适用于应用程序中任何可能使用“魔术字符串”或“魔术数字”的地方。
最容易理解其用法的领域是文件访问。每种文件访问模式(读取、写入、追加)都在枚举中表示。如果您使用“魔术数字”,您可能会得到类似这样的东西:
public static class Constants
{
public static final int FILEMODE_READ = 1;
public static final int FILEMODE_WRITE = 2;
public static final int FILEMODE_APPEND = 3;
}
当您可以使用枚举更清晰、简洁地表达意图时:
public enum FileMode
{
Read,
Write,
Append
}
枚举类型非常适合表示某物的状态/状态。我经常使用以下枚举类型:
public enum PlayerState {
WALKING, STANDING, JUMPING...
}
在枚举和派生类之间总是需要平衡。以一个简单的例子来说明,考虑一个Cat
类。不同的Cat
有不同的攻击性水平。因此,我们可以创建派生类HostileCat
,FriendlyCat
等。然而,还有不同类型的猫,如Lions
,Tigers
等。突然间,我们就有了一堆Cat
对象。因此,另一种解决方案是在Cat
类中提供一个Hostility
枚举,这样就减少了派生类的数量和整体复杂度。
默认答案:所有Java开发人员都应该阅读Effective Java第二版。其中有一章讲解枚举。
enum
/EnumMap
/EnumSet
组合能够出色地实现这些特性。抽象化是好的,enum
是一种强大的抽象化类型,在Java中比在C#和C++中更加强大。 - polygenelubricants