枚举:为什么? 什么时候?
我是一名即将gradle的计算机科学专业的学生,在我的编码生涯中,我发现很少有例子,除了用于表示标准牌组面部的规范案例之外。
你知道在日常编码中使用枚举的任何巧妙方法吗?
为什么枚举如此重要,在什么情况下应该能够确定构建枚举是最好的方法?
这些是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!"); } }
如前面的示例所示,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 employeeOfTheMonth = ... 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... ... }
使用enum
和EnumSet
,它看起来像这样:
enum Button { A, B, X, Y; } Set
参考
- Java语言指南/枚举 - 通过许多示例进行相当完整的处理
也可以看看
- 有效的Java第二版
- 第30项:使用
enum
而不是int
常量 - 第31项:使用实例字段而不是序数
- 第32项:使用
EnumSet
而不是位字段 - 第33项:使用
EnumMap
而不是序数索引
- 第30项:使用
相关问题
- 在Java中的枚举。 优点?
- 是否有类似C的方式从java中的枚举中获取项目编号? - 是的,
ordinal()
,但是...... - 使用Enum的序数值来索引Java中的数组是不好的做法吗? - 很可能,是的!
- [java]每个枚举项的编号? -
EnumSet
的主要候选人!
在Java 1.5之前,您将定义为String
/ 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 }
任何具有多个选项的东西也是如此,例如状态,街道类型(str。,boul。,rd。等),用户访问级别(管理员,主持人,普通用户)等。
请查看此更多示例和说明。
考虑一些简单的代码:
没有枚举:
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程序员,所以请原谅轻微的语法错误)
我想你在谈论的是Enumerations,而不是Enumerators。
枚举非常适合您应用程序中的任何位置,否则您可能会使用“魔术字符串”或“魔术数字”。
了解其用途的最简单方法是在文件访问中。 每个文件访问模式(读,写,追加)都在枚举中表示。 如果你使用的是“魔术数字”,你可能会看到以下内容:
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 }
默认答案:所有Java开发人员都应明确阅读Effective Java Second Edition 。 关于枚举的章节
枚举非常适合表示某事物的状态/状态。 我经常使用以下枚举:
public enum PlayerState { WALKING, STANDING, JUMPING... }
在枚举和派生类之间始终存在平衡。 举一个简单的例子,考虑一个Cat
类。 Cats
有不同程度的敌意。 因此,我们可以制作派生类HostileCat
, FriendlyCat
等。但是,也有不同类型的猫, Lions
, Tigers
等。突然间,我们有一大堆Cat
对象。 因此,另一种解决方案是在Cat
类中提供Hostility
枚举,从而减少派生类的数量和总体复杂性。
正如Manuel Selva所建议的,阅读Effective Java Second Edition ,还阅读了单例部分。 使用enum是拥有干净单例对象的最简单方法 。