A Guide to Java Enums[JAVA Enums 指南]

from:https://www.baeldung.com/a-guide-to-java-enums 

1. 概述

 

在这篇文章中,我们将看到什么是Java 枚举,它解决了什么问题,以及它们在实践中的一些设计模式。

Java 5中引入了"enum"关键字。它表示一种特殊类型的类,该类始终扩展自java.lang.Enum类。有关其使用情况的官方文档,请查看文档

 

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

下面是一个快速而简单的示例,用于定义比萨饼订单的状态;订单状态可以订购就绪交付

public enum PizzaStatus {
    ORDERED,//已订购
    READY, //已准备
    DELIVERED; //已交付
}

此外,它们还具有许多有用的方法,如果您使用传统的公共静态最终常量(public static final constants),你必须自己编写这些方法。

2. 自定义分分方法 

好了,现在我们基本了解了什么是项级以及如何使用它们,让我们通过定义一些额外的 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. 使用"=="运算符比较枚举类型

由于 enum 类型确保 JVM 中只存在一个常量实例,因此我们可以如上安全地使用"=="运算符比较两个变量,"=="运算符还提供编译时和运行时安全。

让我们首先看运行时安全,其中"=="运算符用于比较状态,如果任一值为 null ,则不会引发NullPointerException 。 相反,如果使用等值方法(equals),将引发 NullPointerException:

  if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); //may null
  if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);//not null exception

至于编译时安全,让我们看另一个示例,其中equals 方法比较不同类型的值可能是true - 因为枚举值与getStatus 方法的值恰好一样,但从逻辑上讲,这个比较是错误的(类型不同应该是false)。使用"=="可避免此问题。

编译器将比较标记为不兼容错误:

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

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

 

Enum 类型也可用于switch语句:

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

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

 

您可以在构造函数、方法和字段中定义枚举,使其非常强大。

 

让我们扩展上面的示例,实现披萨状态的过渡,并看看我们如何摆脱之前使用的 if语句和 switch语句:

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

下面的测试片段演示了它是如何工作的:

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

6.枚举集(EnumSet)和枚举图(EnumMap)

6.1.EnumSet

EnumSet 是专用的 Set实现,用于与 Enum 类型一起使用。

与哈希集相比,由于内部使用了位矢量(internal Bit Vector)表示,因此它是高效紧凑的 Enum常数表示形式。它安全地替代了传统基于int 形的位标记 “bit flags”,使我们能够编写易读易维护的代码。

EnumSet是一个抽象类,它有两个实现:"RegularEnumSet""JumboEnumSet",它根据实例化时依赖的常量数来选择。

因此我们最好在需要集合常量时使用EnumSet(如子设置、添加、删除和批量操作(如"包含 All""删除全部")以及使用 Enum.values ()

在下面的代码展示如何使用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.
  }

执行以下测试演示了 Set 接口的EnumSet实现功能

  @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

 

EnumMap是一个专门的映射实现,用于将枚举值作为Key。与对应的HashMap相比,它是一种高效紧凑的实现,在内部表示为数组:

  EnumMap<Pizza.PizzaStatus, Pizza> map;

让我们快速了解一个真实示例,说明如何在实践中使用它:

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

执行以下测试演示了映射接口的 EnumMap实现功能

 
  @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. 使用枚举实现设计模式

7.1. 单例模式

通常,单例模式实现是很简单的。Enums 提供了一种更快速的方法。

此外,由于 enum 类在保护模式下实现可序列化接口,因此 JVM 保证该类是单例,这与常规实现不同,在非序列化期间,我们必须确保不创建任何新实例。

在下面的代码片段中,我们将了解如何实现单例模式:

  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. 战略模式

 

通常策略模式具有不同类实现的接口编写的。

添加新策略意味着添加新的实现类。使用枚举只需少量工作,添加新实现意味着只需实现定义方法即可。

下面的代码段显示了如何实现策略模式:

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

将以下方法添加到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 和 enum

 

在 Java 8 中可以重写 Pizza 类,您可以看到使用 lambdas和流API的方法getAllUndeliveredPizzas()和groupPizzaByStatus()变得如此简洁:

  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. Enum的JSON 描述

使用 Jackson 库,可以像 POJOs 一样具有 JSON 表示方式。下面的代码段显示了可用于相同的 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;
  }
  }

我们可以使用披萨和披萨统计:

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

生成披萨状态的 JSON表示形式

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

有关亿万类的 JSON 序列化/去序列化(包括自定义)的信息,请参阅 Jackson 序列化 作为 JSON 对象

10. 结论

在这篇文章中,我们探讨了Java的枚举,从语言基础知识到更高级和更有趣的实际用例。本文中的代码段可以在Github 存储库中找到。

posted @ 2020-08-10 11:21  CharyGao  阅读(206)  评论(0编辑  收藏  举报