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

发布于 2024-09-12 11:36:33 字数 165 浏览 9 评论 0 原文

我是一名即将毕业的计算机科学专业的学生,​​在我的编码生涯中,我发现很少有使用枚举的实例,除了典型的情况,例如表示一副标准牌的面。

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

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

I am an almost-graduating computer science student, and throughout my coding career, I've found very few instances where enumerations, except for canonical cases such as representing the faces of a standard deck of cards, are used.

Do you know of any clever ways that enums are used in everyday coding?

Why are enumerations so important and in what situations should one be able to identify that building an enumeration is the best approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

故事还在继续 2024-09-19 11:36:33

这些是 enum 的主要参数 EnumMap< code>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< /code> 对应的内容更简洁,更具可读性,并且做出的假设更少(因此出现 bug 的机会更少):

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

实例字段的情况

在 Java 中,enum 是一个 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 第二版

切勿从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 的情况例如

,在 C++ 中,经常使用两个 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...
   ...
}

使用 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 第二版< /em>
    • 第 30 项:使用 enum 代替 int 常量
    • 第 31 项:使用实例字段代替序数
    • 第 32 项:使用 EnumSet< /code> 代替位域
    • 第 33 项:使用 EnumMap< /code> 而不是顺序索引

相关问题

These are the main arguments for enum, EnumMap, and EnumSet by short examples.

The case for enum

As of Java 6, java.util.Calendar is an example of a messy class that could've benefited a lot from using enum (among other improvements).

Currently Calendar defines the following constants (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;
...

These are all int, even though they obviously represent different conceptual entities.

The following are some serious consequences:

  • It's brittle; care must be taken to assign different numbers whenever needed.
    • If by mistake we set MONDAY = 0;, SUNDAY = 0;, then we have MONDAY == SUNDAY
  • There is no namespace and no type-safety, since everything is just an int:
    • We can setMonth(JANUARY), but we can also setMonth(THURSDAY) or setMonth(42)
    • Who knows what set(int,int,int,int,int,int) (a real method!) does!

By contrast, we could have something like this instead:

// Hypothetical enums for a Calendar library
enum Month {
   JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
   SUNDAY, MONDAY, ...
}

Now we never have to worry about MONDAY == SUNDAY (it can never happen!), and since Month and DayOfWeek are different types, setMonth(MONDAY) does not compile.

Additionally, here are some before-and-after codes:

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

Here we're making all sorts of assumptions, e.g. JANUARY + 1 == FEBRUARY, etc. On the other hand, the enum counterpart is both more concise, more readable, and makes less assumptions (and therefore less chance for bugs):

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

The case for instance fields

In Java, enum is a class that has many special properties, but a class nonetheless, allowing you to define instance methods and fields if necessary.

Consider the following example:

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

On the other hand, with enum you can write something like this:

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

The case for instance methods

Consider the following example:

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!");
   }
}

As shown in previous example, enum in Java can have instance methods, but not only that but each constant can have its own specific @Override as well. This is shown in the following code:

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);
}

The case for EnumMap

Here's a quote from Effective Java 2nd Edition:

Never derive a value associated with an enum from its ordinal(); store it in an instance field instead. (Item 31: Use instance fields instead of ordinals) It is rarely appropriate to use ordinals to index arrays: use EnumMap instead. The general principle is that application programmers should rarely, if ever, use Enum.ordinal. (Item 33: Use EnumMap instead of ordinal indexing)

Essentially where as before you may have something like this:

// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...

employeeOfTheMonth[JANUARY] = jamesBond;

Now you can have:

// AFTER, with enum and EnumMap
Map<Month, Employee> employeeOfTheMonth = ...

employeeOfTheMonth.put(Month.JANUARY, jamesBond);

The case for EnumSet

Power of two int constants are often used e.g. in C++ to denote bit sets. This relies on bitwise operations. An example may look something like this:

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

With enum and EnumSet, this can look something like this:

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

References

See also

  • Effective Java 2nd Edition
    • Item 30: Use enum instead of int constants
    • Item 31: Use instance fields instead of ordinals
    • Item 32: Use EnumSet instead of bit fields
    • Item 33: Use EnumMap instead of ordinal indexing

Related questions

寂寞花火° 2024-09-19 11:36:33

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

检查 更多示例和解释请参阅

What you would define as String / int constants before Java 1.5, you should now define as enums. For example, instead of having:

public class StatusConstants {
   public int ACTIVE = 1;
   public int SUSPENDED = 2;
   public int TEMPORARY_INACTIVE = 3;
   public int NOT_CONFIRMED = 4;
}

You now have a safer and more developer-friendly:

public enum Status {
   ACTIVE, SUSPENDED, TEMPORARY_INACTIVE, NOT_CONFIRMED
}

Same goes for anything that has more than one options, like statuses, street types (str., boul., rd., etc), user access levels (administrator, moderator, regular user), etc. etc.

Check this for more examples and explanations.

明月夜 2024-09-19 11:36:33

考虑一些简单的代码:

没有枚举:

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

Consider some simple code:

without enums:

 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)

That look fine, right? Except SetOptions() wants Option B first and then Option A. This will go through the compiler just fine, but when run, sets the options backwards.

Now, with enums:

  enum OptA {On =1, Off = 0};
  enum OptB {On =1, Off = 0};

  SetOption(OptA.On, OptB.Off);// Declaration: SetOption(OptB, OptA)

Now we make the same error, but this time, since enums are different types, the mistake is caught by the compiler.

(NOTE: I'm not really a Java programmer, so please forgive minor syntax errors)

诗化ㄋ丶相逢 2024-09-19 11:36:33

我认为你在谈论枚举,而不是枚举器。

枚举非常适合应用程序中可能使用“魔术字符串”或“魔术数字”的任何地方。

最容易理解其用途的领域是文件访问。每种文件访问模式(读、写、追加)都在枚举中表示。如果您使用“幻数”,您可能会得到如下所示的结果:

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
}

I think you're talking about Enumerations, not Enumerators.

Enumerations are a good fit for anywhere in your application where you might otherwise use "magic strings" or "magic numbers".

The easiest area to understand their use is in File Access. Each file access mode (Read, Write, Append) is represented in the Enumeration. If you were using "magic numbers", you might have something that looks like:

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

When you could express the intent much more clearly and succinctly using an Enumeration:

public enum FileMode
{
    Read,
    Write,
    Append
}
时光与爱终年不遇 2024-09-19 11:36:33

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

Default answer: all Java developer should definitively read Effective Java Second Edition. There is a chapter about enum

木緿 2024-09-19 11:36:33

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

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

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

Enumerations are perfect for representing the state/status of something. I often use enums such as:

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

There's always a balance to be struck between enumerations and derived classes. To give a simple example of this, consider a Cat class. Cats have different levels of hostility. So, we could make derived classes HostileCat, FriendlyCat, etc. However, there are also different types of cats, Lions, Tigers, etc. Suddenly, we have a whole bunch of Cat Objects. So, an alternative solution is to provide a Hostility enumeration in the Cat class, which reduces the number of derived classes and overall complexity.

自由范儿 2024-09-19 11:36:33

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

As Manuel Selva suggested, read Effective Java Second Edition, but also read the singleton section. Using enum is the easiest way to have a clean singleton object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文