A Guide to Java Enums – Java枚举指南

最后修改: 2016年 6月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn what Java enums are, what problems they solve, and how some of their design patterns can be used in practice.

在本教程中,我们将学习什么是Java枚举,它们能解决什么问题,以及它们的一些设计模式如何在实践中使用。

Java 5 first introduced the enum keyword. It denotes a special type of class that always extends the java.lang.Enum class. For the official documentation on usage, we can head over to the documentation.

Java 5首次引入了enum关键字。它表示一种特殊类型的类,它总是扩展java.lang.Enum类。关于用法的官方文档,我们可以前往文档

Constants defined this way make the code more readable, allow for compile-time checking, document the list of accepted values upfront, and avoid unexpected behavior due to invalid values being passed in.

这样定义的常量使代码更易读,允许编译时检查,预先记录可接受的值的列表,并避免由于传入无效的值而导致的意外行为。

Here’s a quick and simple example of an enum that defines the status of a pizza order; the order status can be ORDERED, READY or DELIVERED:

这里有一个快速而简单的例子,是一个定义比萨饼订单状态的枚举;订单状态可以是ORDEREDREADYDELIVERED

public enum PizzaStatus {
    ORDERED,
    READY, 
    DELIVERED; 
}

Additionally, enums come with many useful methods that we would otherwise need to write if we were using traditional public static final constants.

此外,枚举还带有许多有用的方法,如果我们使用传统的公共静态final常量,我们就需要写这些方法。

2. Custom Enum Methods

2.自定义枚举方法

Now that we have a basic understanding of what enums are and how we can use them, we’ll take our previous example to the next level by defining some extra API methods on the enum:

现在我们对什么是枚举以及如何使用枚举有了基本的了解,我们将通过在枚举上定义一些额外的API方法来把我们之前的例子提升到新的高度。

public class Pizza {
    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED,
        READY,
        DELIVERED;
    }

    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            return true;
        }
        return false;
    }
    
    // Methods that set and get the status variable.
}

3. Comparing Enum Types Using “==” Operator

3.使用”==”运算符比较枚举类型

Since enum types ensure that only one instance of the constants exist in the JVM, we can safely use the “==” operator to compare two variables, like we did in the above example. Furthermore, the “==” operator provides compile-time and run-time safety.

由于枚举类型确保在JVM中只存在一个常量的实例,我们可以安全地使用”==”操作符来比较两个变量,就像我们在上面的例子中做的那样。此外,”==”运算符提供了编译时和运行时的安全性。

First, we’ll look at run-time safety in the following snippet, where we’ll use the “==” operator to compare statuses. Either value can be null and we won’t get a NullPointerException. Conversely, if we use the equals method, we will get a NullPointerException:

首先,我们将在下面的代码段中查看运行时安全,我们将使用”==”操作符来比较状态。任何一个值都可以是null,我们不会得到一个NullPointerException。反之,如果我们使用equals方法,我们将得到一个NullPointerException

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); 
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); 

As for compile-time safety, let’s look at an example where we’ll determine that an enum of a different type is equal by comparing it using the equals method. This is because the values of the enum and the getStatus method coincidentally are the same; however, logically the comparison should be false. We avoid this issue by using the “==” operator.

至于编译时安全性,让我们看一个例子,我们将通过使用equals方法进行比较来确定不同类型的枚举是相等的。这是因为枚举的值和getStatus方法不约而同地是一样的;然而,从逻辑上讲,比较应该是假的。我们通过使用”==”操作符来避免这个问题。

The compiler will flag the comparison as an incompatibility error:

编译器会将这种比较标记为不兼容错误。

if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);

4. Using Enum Types in Switch Statements

4.在开关语句中使用枚举类型

We can use enum types in switch statements also:

我们也可以在switch语句中使用枚举类型。

public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return 5;
        case READY: return 2;
        case DELIVERED: return 0;
    }
    return 0;
}

5. Fields, Methods and Constructors in Enums

5.枚举中的字段、方法和构造函数

We can define constructors, methods, and fields inside enum types, which makes them very powerful.

我们可以在枚举类型中定义构造函数、方法和字段,这使得它们非常强大。

Next, let’s extend the example above by implementing the transition from one stage of a pizza order to another. We’ll see how we can get rid of the if and switch statements used before:

接下来,让我们扩展上面的例子,实现从披萨订单的一个阶段过渡到另一个阶段。我们将看到我们如何摆脱之前使用的ifswitch语句。

public class Pizza {

    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED (5){
            @Override
            public boolean isOrdered() {
                return true;
            }
        },
        READY (2){
            @Override
            public boolean isReady() {
                return true;
            }
        },
        DELIVERED (0){
            @Override
            public boolean isDelivered() {
                return true;
            }
        };

        private int timeToDelivery;

        public boolean isOrdered() {return false;}

        public boolean isReady() {return false;}

        public boolean isDelivered(){return false;}

        public int getTimeToDelivery() {
            return timeToDelivery;
        }

        PizzaStatus (int timeToDelivery) {
            this.timeToDelivery = timeToDelivery;
        }
    }

    public boolean isDeliverable() {
        return this.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " + 
          this.getStatus().getTimeToDelivery());
    }
    
    // Methods that set and get the status variable.
}

The test snippet below demonstrates how this works:

下面的测试片段演示了这是如何工作的。

@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
    Pizza testPz = new Pizza();
    testPz.setStatus(Pizza.PizzaStatus.READY);
    assertTrue(testPz.isDeliverable());
}

6. EnumSet and EnumMap

6.EnumSetEnumMap

6.1. EnumSet

6.1.EnumSet

The EnumSet is a specialized Set implementation that’s meant to be used with Enum types.

EnumSet是一个专门的Set实现,旨在用于Enum类型。

Compared to a HashSet, it’s a very efficient and compact representation of a particular Set of Enum constants, owing to the internal Bit Vector Representation that’s used. It also provides a type-safe alternative to traditional int-based “bit flags,” allowing us to write concise code that’s more readable and maintainable.

HashSet相比,由于使用了内部的Bit Vector Representation,它是Enum常量的特定Set的一个非常有效和紧凑的表示。它还为传统的基于int的 “位标志 “提供了一个类型安全的替代方案,使我们能够编写更易读和更易维护的简洁代码。

The EnumSet is an abstract class that has two implementations, RegularEnumSet and JumboEnumSet, one of which is chosen depending on the number of constants in the enum at the time of instantiation.

EnumSet是一个抽象类,它有两个实现,RegularEnumSetJumboEnumSet,其中一个是根据实例化时枚举中常量的数量来选择的。

Therefore, it’s a good idea to use this set whenever we want to work with a collection of enum constants in most scenarios (like subsetting, adding, removing, and bulk operations like containsAll and removeAll), and use Enum.values() if we just want to iterate over all possible constants.

因此,只要我们想在大多数情况下处理枚举常量的集合(比如子集、添加、删除以及像containAllremoveAll这样的批量操作),使用这个集合是个好主意,如果我们只想在所有可能的常量上进行迭代,则使用Enum.values()

In the code snippet below, we can see how to use EnumSet to create a subset of constants:

在下面的代码片段中,我们可以看到如何使用EnumSet来创建一个常量的子集。

public class Pizza {

    private static EnumSet<PizzaStatus> undeliveredPizzaStatuses =
      EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);

    private PizzaStatus status;

    public enum PizzaStatus {
        ...
    }

    public boolean isDeliverable() {
        return this.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " + 
          this.getStatus().getTimeToDelivery() + " days");
    }

    public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
        return input.stream().filter(
          (s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
            .collect(Collectors.toList());
    }

    public void deliver() { 
        if (isDeliverable()) { 
            PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
              .deliver(this); 
            this.setStatus(PizzaStatus.DELIVERED); 
        } 
    }
    
    // Methods that set and get the status variable.
}

Executing the following test demonstrates the power of the EnumSet implementation of the Set interface:

执行下面的测试展示了EnumSet接口的Set实现的力量。

@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); 
    assertTrue(undeliveredPzs.size() == 3); 
}

6.2. EnumMap

6.2.EnumMap[/strong

EnumMap is a specialized Map implementation meant to be used with enum constants as keys. Compared to its counterpart HashMap, it’s an efficient and compact implementation that’s internally represented as an array:

EnumMap是一个专门的Map实现,旨在将枚举常量作为键来使用。与其对应的HashMap相比,它是一个高效而紧凑的实现,内部表示为一个数组。

EnumMap<Pizza.PizzaStatus, Pizza> map;

Let’s look at an example of how we can use it in practice:

让我们看一个例子,看看我们如何在实践中使用它。

public static EnumMap<PizzaStatus, List<Pizza>> 
  groupPizzaByStatus(List<Pizza> pizzaList) {
    EnumMap<PizzaStatus, List<Pizza>> pzByStatus = 
      new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);
    
    for (Pizza pz : pizzaList) {
        PizzaStatus status = pz.getStatus();
        if (pzByStatus.containsKey(status)) {
            pzByStatus.get(status).add(pz);
        } else {
            List<Pizza> newPzList = new ArrayList<Pizza>();
            newPzList.add(pz);
            pzByStatus.put(status, newPzList);
        }
    }
    return pzByStatus;
}

Executing the following test demonstrates the power of the EnumMap implementation of the Map interface:

执行下面的测试展示了EnumMap接口的Map实现的力量。

@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    EnumMap<Pizza.PizzaStatus,List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
    assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
    assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
    assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}

7. Implement Design Patterns Using Enums

7.使用枚举实现设计模式

7.1. Singleton Pattern

7.1.单子模式

Normally, implementing a class using the Singleton pattern is quite non-trivial. Enums provide a quick and easy way of implementing singletons.

通常情况下,使用Singleton模式实现一个类是相当不容易的。枚举提供了一种实现单子的快速而简单的方法。

In addition, since the enum class implements the Serializable interface under the hood, the class is guaranteed to be a singleton by the JVM. This is unlike the conventional implementation, where we have to ensure that no new instances are created during deserialization.

此外,由于枚举类在内部实现了Serializable接口,JVM保证该类是一个单子。这与传统的实现不同,在传统的实现中,我们必须确保在反序列化过程中不创建新的实例。

In the code snippet below, we see how we can implement a singleton pattern:

在下面的代码片断中,我们可以看到如何实现单子模式。

public enum PizzaDeliverySystemConfiguration {
    INSTANCE;
    PizzaDeliverySystemConfiguration() {
        // Initialization configuration which involves
        // overriding defaults like delivery strategy
    }

    private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;

    public static PizzaDeliverySystemConfiguration getInstance() {
        return INSTANCE;
    }

    public PizzaDeliveryStrategy getDeliveryStrategy() {
        return deliveryStrategy;
    }
}

7.2. Strategy Pattern

7.2.战略模式

Conventionally, the Strategy pattern is written by having an interface that is implemented by different classes.

传统上,策略模式是通过拥有一个由不同类实现的接口来编写的。

Adding a new strategy means adding a new implementation class. With enums, we can achieve this with less effort, and adding a new implementation means simply defining another instance with some implementation.

添加一个新的策略意味着添加一个新的实现类。有了枚举,我们就可以事半功倍地实现这一点,添加一个新的实现意味着简单地定义另一个具有某种实现的实例。

The code snippet below shows how to implement the Strategy pattern:

下面的代码片断显示了如何实现策略模式。

public enum PizzaDeliveryStrategy {
    EXPRESS {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in express mode");
        }
    },
    NORMAL {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in normal mode");
        }
    };

    public abstract void deliver(Pizza pz);
}

Then we add the following method to the Pizza class:

然后我们在Pizza类中添加以下方法。

public void deliver() {
    if (isDeliverable()) {
        PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
          .deliver(this);
        this.setStatus(PizzaStatus.DELIVERED);
    }
}
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
    Pizza pz = new Pizza();
    pz.setStatus(Pizza.PizzaStatus.READY);
    pz.deliver();
    assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}

8. Java 8 and Enums

Java 8和枚举

We can rewrite the Pizza class in Java 8, and see how the methods getAllUndeliveredPizzas() and groupPizzaByStatus() become so concise with the use of lambdas and the Stream APIs:

我们可以在Java 8中重写Pizza类,看看getAllUndeliveredPizzas()groupPizzaByStatus()方法如何通过使用lambdas和Stream API变得如此简明。

public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
    return input.stream().filter(
      (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
        .collect(Collectors.toList());
}

public static EnumMap<PizzaStatus, List<Pizza>> 
  groupPizzaByStatus(List<Pizza> pzList) {
    EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
      Collectors.groupingBy(Pizza::getStatus,
      () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
    return map;
}

9. JSON Representation of Enum

9.Enum的JSON表示法

Using Jackson libraries, it’s possible to have a JSON representation of enum types as if they’re POJOs. In the code snippet below, we’ll see how we can use the Jackson annotations for the same:

使用Jackson库,有可能让枚举类型的JSON表示,就像它们是POJO一样。在下面的代码片段中,我们将看到我们如何使用Jackson注解来实现这一目标。

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
    ORDERED (5){
        @Override
        public boolean isOrdered() {
            return true;
        }
    },
    READY (2){
        @Override
        public boolean isReady() {
            return true;
        }
    },
    DELIVERED (0){
        @Override
        public boolean isDelivered() {
            return true;
        }
    };

    private int timeToDelivery;

    public boolean isOrdered() {return false;}

    public boolean isReady() {return false;}

    public boolean isDelivered(){return false;}

    @JsonProperty("timeToDelivery")
    public int getTimeToDelivery() {
        return timeToDelivery;
    }

    private PizzaStatus (int timeToDelivery) {
        this.timeToDelivery = timeToDelivery;
    }
}

We can use the Pizza and PizzaStatus as follows:

我们可以使用PizzaPizzaStatus,如下。

Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));

This will generate the following JSON representation of the Pizzas status:

这将产生以下关于Pizzas状态的JSON表示。

{
  "status" : {
    "timeToDelivery" : 2,
    "ready" : true,
    "ordered" : false,
    "delivered" : false
  },
  "deliverable" : true
}

For more information on JSON serializing/deserializing (including customization) of enum types, we can refer to the Jackson – Serialize Enums as JSON Objects.

关于枚举类型的JSON序列化/反序列化(包括自定义)的更多信息,我们可以参考Jackson – Serialize Enums as JSON Objects

10. Conclusion

10.结语

In this article, we explored the Java enum, from the language basics to more advanced and interesting real-world use cases.

在这篇文章中,我们探讨了Java枚举,从语言基础知识到更高级和有趣的现实世界用例。

Code snippets from this article can be found in the Github repository.

本文的代码片段可以在Github资源库中找到。