Java 枚举(enum)

原文: https://javabeginnerstutorial.com/core-java-tutorial/java-enum-enumerations/

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

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

为什么要使用枚举?

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

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

  1. public class Schedule {
  2. private ??? workday;
  3. }

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

  1. public class Workdays {
  2. public static final String MONDAY = "Monday";
  3. public static final String TUESDAY = "Tuesday";
  4. public static final String WEDNESDAY = "Wednesday";
  5. public static final String THURSDAY = "Thursday";
  6. public static final String FRIDAY = "Friday";
  7. }

现在,Schedule类将如下所示:

  1. public class Schedule {
  2. // Workdays.MONDAY, Workdays.TUESDAY
  3. // Workdays.WEDNESDAY, Workdays.THURSDAY
  4. // Workdays.FRIDAY
  5. private String workday;
  6. }

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

为了解决这个问题,我们将Workdays类转换为一个枚举:

  1. public enum Workday {
  2. MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
  3. }

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

  1. public class Schedule {
  2. private Workday workday;
  3. public Workday getWorkday() {
  4. return this.workday;
  5. }
  6. public void setWorkday(Workday workday) {
  7. this.workday = workday;
  8. }
  9. }

条件和枚举

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

  1. public final boolean equals(Object other) {
  2. return this==other;
  3. }

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

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

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

或在switch块中:

  1. switch(currentSchedule.getWorkday()) {
  2. case MONDAY:
  3. case TUESDAY:
  4. case WEDNESDAY:
  5. case THURSDAY:
  6. System.out.println("Working and working and...");
  7. break;
  8. case FRIDAY:
  9. System.out.println("Hurray! Tomorrow is weekend!");
  10. break;
  11. default:
  12. System.out.println("Unknown day -.-");
  13. }

迭代枚举

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

  1. for(Workday w : Workday.values()) {
  2. System.out.println(w.name());
  3. }

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

  1. MONDAY
  2. TUESDAY
  3. WEDNESDAY
  4. THURSDAY
  5. FRIDAY

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

枚举字段

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

  1. public enum Workday {
  2. MONDAY("Monday"),
  3. TUESDAY("Tuesday"),
  4. WEDNESDAY("Wednesday"),
  5. THURSDAY("Thursday"),
  6. FRIDAY("Friday");
  7. private final String representation;
  8. private Workday(String representation) {
  9. this.representation = representation;
  10. }
  11. }

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

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

枚举方法

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

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

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

  1. public enum Workday {
  2. MONDAY("Monday"),
  3. TUESDAY("Tuesday"),
  4. WEDNESDAY("Wednesday"),
  5. THURSDAY("Thursday"),
  6. FRIDAY("Friday");
  7. private final String representation;
  8. private Workday(String representation) {
  9. this.representation = representation;
  10. }
  11. public String getRepresentation() {
  12. return this.representation;
  13. }
  14. }

现在更新迭代:

  1. for(Workday w : Workday.values()) {
  2. System.out.println(w.getRepresentation());
  3. }

现在,结果如下:

  1. Monday
  2. Tuesday
  3. Wednesday
  4. Thursday
  5. Friday

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

实现接口

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

一种这样的用例是使用 JPA 将枚举值存储在数据库中-尽管不是按名称或顺序(可通过@EnumerationEnumType获得),而是通过短代码。

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

  1. public interface DatabaseEnum<T extends Enum<T>> {
  2. /**
  3. * Converts the database representation back to the enumeration value
  4. */
  5. T fromDatabase(String representation);
  6. /**
  7. * Converts the enum value to the database representation
  8. */
  9. String toDatabaseString();
  10. }

该接口使用泛型(由T类型表示),并在String和枚举类型之间进行转换。

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

  1. public enum Workday implements DatabaseEnum<Workday>{
  2. MONDAY("Monday"),
  3. TUESDAY("Tuesday"),
  4. WEDNESDAY("Wednesday"),
  5. THURSDAY("Thursday"),
  6. FRIDAY("Friday");
  7. private final String representation;
  8. private Workday(String representation) {
  9. this.representation = representation;
  10. }
  11. public String getRepresentation() {
  12. return this.representation;
  13. }
  14. @Override
  15. public String toDatabaseString() {
  16. return this.representation;
  17. }
  18. @Override
  19. public Workday fromDatabase(String representation) {
  20. for(Workday wd: Workday.values()) {
  21. if(wd.representation.equals(representation)) {
  22. return wd;
  23. }
  24. }
  25. return null;
  26. }
  27. }

使用枚举而不是布尔值

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

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

  1. public void someMethod(String someParameter, boolean rightHandSide) {
  2. if(!rightHandSide) {
  3. // do something
  4. }
  5. }

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

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

  1. public enum Side {
  2. RIGHT_HAND,
  3. LEFT_HAND
  4. }
  5. public void someMethod(String someParameter, Side side) {
  6. if(side == Side.RIGHT_HAND) {
  7. // do something
  8. }
  9. }

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

总结

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