当前位置: 首页 > 面试题库 >

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

许嘉福
2023-03-14
问题内容

我是一名即将毕业的计算机科学专业的学生,​​在我的整个编码生涯中,我发现很少使用枚举的实例,除了典型的情况(例如代表标准纸牌的面孔)外,还使用了枚举。

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

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


问题答案:

这些是主要的论点enumEnumMap以及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(它永远不会发生!),并且由于MonthDayOfWeek是不同的类型,因此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();
而是将其存储在实例字段中。( 第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
++中经常使用两个常数的幂来表示位集。这依赖于按位运算。一个例子可能看起来像这样:

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

参考文献

  • Java语言指南/枚举 -包含许多示例的相当完整的介绍

也可以看看

  • 有效的Java 2nd Edition
    • 条款30:使用enum而不是int常量
    • 第31项:使用实例字段代替普通字段
    • 项目32:使用EnumSet代替位字段
    • 项目33:使用EnumMap而不是顺序索引


 类似资料:
  • 我听到一些人建议在C++中使用枚举类,因为它们的类型安全。 但这到底意味着什么呢?

  • 问题内容: 今天,我浏览了该站点上的一些问题,发现提到了 以单例模式使用的这种解决方案声称具有线程安全性的优点。 我从未使用过,并且使用Java编程已经有两年多了。显然,他们改变了很多。现在,他们甚至在自己内部提供了对OOP的全面支持。 现在为什么要在日常编程中使用枚举?为什么? 问题答案: 当变量(尤其是方法参数)只能从一小部分可能的值中取出一个时,应始终使用枚举。例如类型常量(合同状态:“永久

  • 问题内容: 在Java 5及更高版本中,您具有foreach循环,该循环可以神奇地实现任何实现的对象: 但是,仍然没有实现,这意味着要迭代一个,您必须执行以下操作: 有谁知道为什么仍然不执行? 编辑: 为澄清起见,我不是在谈论枚举的语言概念,而是在Java API中称为“ 枚举 ” 的Java特定类。 问题答案: 枚举没有被修改为支持Iterable,因为它是一个接口,而不是一个具体的类(例如Ve

  • 问题内容: 我刚刚发现Java允许枚举实现接口。有什么好的用例? 问题答案: 枚举不仅仅代表被动集(例如颜色)。他们可以代表与功能更复杂的对象,所以你可能想进一步功能添加到这些是那么-例如,你可能如接口,等等。支持这些和组件。

  • 问题内容: 我想知道为什么在Java语言中a 不能扩展。 我不是在谈论一个延伸的(这不能做,因为Java没有多重继承,而Š隐含延长),但一类的以只添加额外的方法,而不是额外的枚举值。 就像是: 要这样使用: 因此,有人可以对此限制提供理由(或将我指向正确的JLS部分)吗? 问题答案: 我认为 他们这样做 的答案来自以下问题: 在您的示例中,如何实例化MyClass?用户永远不会(通过)显式实例化枚

  • 问题内容: 如果语言设计者仅使用Enum 会对语言产生怎样的影响? 现在唯一的区别是有人会写 A扩展 但由于Java中不允许扩展枚举,因此仍然是非法的。我也在考虑为jvm提供一个字节码,该字节码将smth定义为扩展枚举-但是泛型不会受到影响,因为它们都被删除了。 那么,声明的重点是什么? 谢谢! 编辑 为简单起见,让我们来看一个例子: 这个类结构有什么问题?限制可以做什么? 问题答案: 这是一个普