zoukankan      html  css  js  c++  java
  • 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:

      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 代表 Enum

     

    使用 Jackson 库,可以像 POJOs 一样具有重则小的 JSON 表示类型。下面的代码段显示了可用于相同的杰克逊注释:

      @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 存储库中找到。

    我刚刚宣布了新的学习春季课程, 

  • 相关阅读:
    转贴:Asp.Net 学习资源列表
    实现简单的写字板
    android绘图—Paint path 旋转
    Eclipse Android编程快捷键
    android Bitmap学习总结
    各种颜色对应的十六进制数
    Android surfaceView 与View 的区别
    SQLite设置_id自增的方法
    数据库表外键设置
    android自定义View的用法
  • 原文地址:https://www.cnblogs.com/Chary/p/13468700.html
Copyright © 2011-2022 走看看