# Java 枚举（`enum`）

> 原文： [https://javabeginnerstutorial.com/core-java-tutorial/java-enum-enumerations/](https://javabeginnerstutorial.com/core-java-tutorial/java-enum-enumerations/)

在本文中，我将介绍 Java 枚举，这是在应用中定义和使用常量的最优雅的方式。

这是每个人都知道的基本功能，但还有一些您可能不知道的功能。

## 为什么要使用枚举？

好吧，您在 Java 代码中使用了枚举。 如果您不这样做，那么您做错了什么，或者拥有非常简单的应用而没有太多复杂功能。

无论如何，让我们看一下基础知识。 例如，您想要一个使用工作日的类。 您可以这样定义它：

```java
 public class Schedule {
     private ??? workday;
   }
```

要存储工作日，我们创建一个工具类来存储工作日的常量：

```java
public class Workdays {
public static final String MONDAY = "Monday";
public static final String TUESDAY = "Tuesday";
public static final String WEDNESDAY = "Wednesday";
public static final String THURSDAY = "Thursday";
public static final String FRIDAY = "Friday";
}
```

现在，`Schedule`类将如下所示：

```java
public class Schedule {
// Workdays.MONDAY, Workdays.TUESDAY
// Workdays.WEDNESDAY, Workdays.THURSDAY
// Workdays.FRIDAY
private String workday;
}
```

我想您已经看到了这种方法的缺点：即使`Workdays`类中未定义工作日，也可以轻松地将工作日设置为“星期六”或“星期日”。 此解决方案不是值安全的。 这不是我们想要实现的。

为了解决这个问题，我们将`Workdays`类转换为一个枚举：

```java
public enum Workday {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
}
```

所需的打字次数更少，现在该解决方案是值安全的。 我们只需要调整`Schredule`类：

```java
public class Schedule {
private Workday workday;

public Workday getWorkday() {
   return this.workday;
}

public void setWorkday(Workday workday) {
   this.workday = workday;
}
}
```

## 条件和枚举

枚举非常适合条件表达式（`if`语句或`switch`块）。 关于枚举的好处是它们是常量值，因此您可以将它们与`==`运算符进行比较，该运算符比使用`equals()`更优雅-并且避免了可能的`NullPointerExceptions`。 顺便说一句：如果您查看`Enum`类`equals()`的实现，您将看到以下内容：

```java
public final boolean equals(Object other) {
   return this==other;
}
```

因此，请自由使用`==`，因为这是`equals()`的默认实现。

让我们看一下如何使用它们的示例：

```java
if(currentSchedule.getWorkday() == Workday.FRIDAY) {
System.out.println("Hurray! Tomorrow is weekend!");
}
```

或在`switch`块中：

```java
switch(currentSchedule.getWorkday()) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
   System.out.println("Working and working and...");
   break;
case FRIDAY:
   System.out.println("Hurray! Tomorrow is weekend!");
   break;
default:
   System.out.println("Unknown day -.-");
}
```

## 迭代枚举

迭代枚举非常简单。 枚举类定义了一个称为`values()`的方法，该方法返回给定枚举的值。 最好是看一个示例：

```java
for(Workday w : Workday.values()) {
System.out.println(w.name());
}
```

上面的示例将产生以下输出：

```java
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
```

如您所见，`values()`的顺序与枚举本身中定义的顺序相同。 因此，Java 不会进行任何魔术排序。

## 枚举字段

有时，您只想将枚举打印到控制台（或以某种 UI 形式显示）。 在上面的示例（工作日）中，您可以简单地使用枚举的名称，尽管有时“`TUESDAY`”似乎很喊，而“`Tuesday`”更可取。 在这种情况下，您可以添加和使用`Enum`对象的自定义字段：

```java
public enum Workday {
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday");

private final String representation;

private Workday(String representation) {
   this.representation = representation;
}
}
```

如您在上面的示例中所看到的，枚举获取一个私有字段，在这种情况下称为表示形式。 该字段是最终字段，因此以后无法更改，这意味着必须在枚举构造期间初始化此属性。 这是通过构造器完成的，并且提供了构造器参数以及枚举定义。

您可以根据需要在枚举中拥有任意数量的属性/字段，但是我建议您将这个数量保持在较低的水平，因为具有 15 个额外属性的枚举确实很奇怪。 在这种情况下，我将考虑使用一个类和/或多个枚举来保存相同的信息。

## 枚举方法

枚举字段很好，但是如何访问该字段？ 我告诉过您，新的表示形式是表示该枚举的值，但是当前我们无法在枚举本身之外访问该属性。

除此之外，还有一个基本方法可用于所有枚举：`name()`以字符串形式返回当前值的名称。 这意味着在基本情况下，您可以使用此方法显示枚举的值（例如，在 UI 或日志条目中）。 **当然**也存在`toString()`函数，但是有时开发人员会覆盖它，以使其对程序员更友好（或用户友好？）显示。 作为最佳实践，如果要显示枚举的名称，建议您使用`name()`方法而不是`toString()`。

要从上面更改表示示例（当我们遍历`values()`时），只需编写一个函数，该函数将返回新的变量表示并在迭代中使用它：

```java
public enum Workday {
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday");

private final String representation;

private Workday(String representation) {
   this.representation = representation;
}

public String getRepresentation() {
   return this.representation;
}
}
```

现在更新迭代：

```java
for(Workday w : Workday.values()) {
System.out.println(w.getRepresentation());
}
```

现在，结果如下：

```java
Monday
Tuesday
Wednesday
Thursday
Friday
```

但这不是唯一可以实现枚举方法的用法。 在下一节中，我们将看到如何将`Enum`值映射到`String`并返回。

## 实现接口

关于枚举鲜为人知的一件事是它们可以实现接口。 这意味着，如果您需要不同枚举所需要的通用功能，则可以使用一个接口定义它，而枚举必须在该接口中实现方法。

一种这样的用例是使用 JPA 将枚举值存储在数据库中-尽管不是按名称或顺序（可通过`@Enumeration`和`EnumType`获得），而是通过短代码。

在这种情况下，您可以创建一个接口，其中包含将枚举转换为数据库表示形式并将枚举转换回的方法。

```java
public interface DatabaseEnum<T extends Enum<T>> {

   /**
     * Converts the database representation back to the enumeration value
     */
   T fromDatabase(String representation);

   /**
     * Converts the enum value to the database representation
     */
   String toDatabaseString();

}
```

该接口使用泛型（由`T`类型表示），并在`String`和枚举类型之间进行转换。

要实现此接口，请扩展“工作日”示例：

```java
public enum Workday implements DatabaseEnum<Workday>{
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday");

private final String representation;

private Workday(String representation) {
   this.representation = representation;
}

public String getRepresentation() {
   return this.representation;
}

@Override
public String toDatabaseString() {
   return this.representation;
}

@Override
public Workday fromDatabase(String representation) {
   for(Workday wd: Workday.values()) {
     if(wd.representation.equals(representation)) {
       return wd;
     }
   }
   return null;
}
}
```

## 使用枚举而不是布尔值

有时，您最终得到一个采用布尔表达式的方法（或者您仅获得一个告诉您使用布尔值的项目说明，但感觉很痒）。 在这种情况下，可以随意引入一个新的枚举并使用正确的值而不是布尔值。

例如，一旦我有了一个规范，告诉我必须创建一个带有一些参数的方法和一个布尔值，称为“`rightHandSide`”。 实际上，“`rightHandSide`”的默认值为`false`。 首先，我按照规范中的说明实现了该方法，但是这对我来说并不舒服，我认为这是摆脱这种冷酷感觉的另一种方法：

```java
public void someMethod(String someParameter, boolean rightHandSide) {
if(!rightHandSide) {
   // do something
}
}
```

好吧，这似乎并不坏，但是如何扩展功能呢？ 如果一方不重要怎么办？ 在这种情况下，可以使用其他布尔参数扩展该方法，但从长远来看，它不会成功。 而且因为“`rightHandSide`”的默认值为`false`，所以我需要创建一个未提供参数的调用，并且使用`false`调用默认方法。

因此，我引入了一个名为`Side`的新枚举，并替换了布尔参数：

```java
public enum Side {
RIGHT_HAND,
LEFT_HAND
}

public void someMethod(String someParameter, Side side) {
if(side == Side.RIGHT_HAND) {
   // do something
}
}
```

现在感觉好多了，以后可以将该方法扩展到更多情况。

## 总结

如您所见，枚举非常适合替换常量-有时也可以使用布尔方法参数。

