枚举:为什么?什么时候?

时间:2010-07-29 13:43:43

标签: java enums enumeration

我是一名即将毕业的计算机科学专业的学生,​​在整个编码生涯中,我发现很少有例子,除了用于表示标准牌组面部的规范案例之外,其中使用了枚举。

您是否知道在日常编码中使用枚举的任何聪明方法?

为什么枚举如此重要,在什么情况下应该能够确定构建枚举是最好的方法?

7 个答案:

答案 0 :(得分:29)

这些是enumEnumMapEnumSet的主要参数。

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
    • 我们可以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(它永远不会发生!),并且由于MonthDayOfWeek是不同的类型,setMonth(MONDAY)不会编译。< / p>

此外,这里有一些前后代码:

// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
   ...
}

我们在这里做出各种各样的假设,例如: JANUARY + 1 == FEBRUARY等等。另一方面,enum对应文件更简洁,更易读,并且假设更少(因此错误的机会更少):

// AFTER with enum
for (Month month : Month.values()) {
   ...
}

例如字段

的情况

在Java中,enumclass,它有许多特殊属性,但是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...
   ...
}

使用enumEnumSet,可以看起来像这样:

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...
   ...
}

参考

另见

  • Effective Java 2nd Edition
    • 第30项:使用enum代替int常量
    • 第31项:使用实例字段而不是序数
    • 第32项:使用EnumSet代替位字段
    • 第33项:使用EnumMap代替有序索引

相关问题

答案 1 :(得分:7)

在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。等),用户访问级别(管理员,主持人,普通用户)等等。

查看this以获取更多示例和解释。

答案 2 :(得分:5)

考虑一些简单的代码:

没有枚举:

 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程序员,所以请原谅轻微的语法错误)

答案 3 :(得分:3)

我认为你在谈论枚举,而不是普查员。

枚举非常适合您应用程序中的任何位置,否则您可能会使用“魔术字符串”或“幻数”。

了解其用途的最简单方法是在文件访问中。每个文件访问模式(读,写,追加)都在枚举中表示。如果你使用的是“魔术数字”,你可能会有类似的东西:

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
}

答案 4 :(得分:2)

默认答案:所有Java开发人员都应明确阅读Effective Java Second Edition。有一章关于枚举

答案 5 :(得分:2)

枚举非常适合表示某事物的状态/状态。我经常使用枚举如:

public enum PlayerState {
    WALKING, STANDING, JUMPING...
}

在枚举和派生类之间始终存在平衡。举一个简单的例子,考虑一个Cat类。 Cats有不同程度的敌意。因此,我们可以制作派生类HostileCatFriendlyCat等。但是,也有不同类型的猫,LionsTigers等。突然间,我们有一个一大堆Cat个对象。因此,另一种解决方案是在Hostility类中提供Cat枚举,这样可以减少派生类的数量和总体复杂性。

答案 6 :(得分:1)

正如Manuel Selva建议的那样,阅读 Effective Java Second Edition ,但也阅读了单例部分。 使用枚举是获得干净的单件对象的最简单方法