Java Enum简介

2,349 阅读5分钟
原文链接: www.ripjava.com

1. 概述

在本文中,我们将看到Java枚举是什么,它们解决了哪些问题以及它们在实践中如何使用它们的设计模式。

枚举关键字在Java 5中引入它是指一种特殊类型的类,这些类总是扩展java.lang.Enum中的类。

有关其使用的官方文档,请查看文档

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

下面是一个简单的枚举示例,用于定义外卖订单的状态;

订单状态可以是ORDEREDREADYDELIVERED

public enum PizzaStatus {
    ORDERED,
    READY, 
    DELIVERED; 
}

此外,它们还带有许多有用的方法,如果使用传统的公共静态常量,则必须自己编写。

2. 自定义枚举方法

好了,现在我们已经基本了解了枚举是什么以及如何使用它们,下面通过在枚举上定义一些额外的API方法将我们之前的示例提升到新的水平:

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

    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            return true;
        }
        return false;
    }

    public PizzaStatus getStatus() {
        return status;
    }

    public void setStatus(PizzaStatus status) {
        this.status = status;
    }
}

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

由于枚举类型确保JVM中只存在一个常量实例,因此我们可以安全地使用“==”运算符来比较两个变量,此外,“==”运算符提供编译时和运行时安全性。

让我们首先看一下下面代码片段中的运行时安全性,其中“==”运算符用于比较状态, 如果任一值为null,则不会抛出NullPointerException。 相反,如果使用equals方法,则抛出NullPointerException

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

至于编译时安全性,让我们看看另一个例子,其中使用equals方法比较不同类型的枚举被确定为真 。

因为枚举和getStatus方法的值巧合地是相同的,但逻辑上是比较应该是假的。

使用“==”运算符可以避免此问题。

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

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

4. 在switch中使用枚举类型

枚举类型也可以在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 Pizza2 {
    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());
    }

    public PizzaStatus getStatus() {
        return status;
    }

    public void setStatus(PizzaStatus status) {
        this.status = status;
    }
}

下面的测试代码段演示了这是如何工作的:

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

6. EnumSet和EnumMap

6.1. EnumSet

该EnumSet是一家专门为了存储枚举类型的Set。

EnumSet是一个抽象类,有两个子类分别是RegularEnumSet和JumboEnumSet。

在下面的代码片段中,您可以看到如何使用EnumSet创建常量子集及其用法:

public class Pizza3 {

    private static EnumSet<PizzaStatusEnum> deliveredPizzaStatuses = EnumSet.of(PizzaStatusEnum.DELIVERED);

    private PizzaStatusEnum status;
    
     public static List<Pizza3> getAllUndeliveredPizzas(List<Pizza3> input) {
        return input.stream().filter((s) -> !deliveredPizzaStatuses.contains(s.getStatus())).collect(Collectors.toList());
    }
    public void deliver() {
        if (isDeliverable()) {
              	        PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy().deliver(this);
              this.setStatus(PizzaStatusEnum.DELIVERED);
          }
      } 
}

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

@Test
    public void test_RetrievingUnDeliveredPzs() {
        List<Pizza3> pzList = new ArrayList<>();
        Pizza3 pz1 = new Pizza3();
        pz1.setStatus(Pizza3.PizzaStatusEnum.DELIVERED);

        Pizza3 pz2 = new Pizza3();
        pz2.setStatus(Pizza3.PizzaStatusEnum.ORDERED);

        Pizza3 pz3 = new Pizza3();
        pz3.setStatus(Pizza3.PizzaStatusEnum.ORDERED);

        Pizza3 pz4 = new Pizza3();
        pz4.setStatus(Pizza3.PizzaStatusEnum.READY);

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

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

6.2 EnumMap的

EnumMap是一个专门的Map实现,为在与枚举常量一起使用作为键。内部实现是使用数组。

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

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

执行以下测试演示了Map接口的EnumMap实现的强大功能:

    @Test
    public void test_GroupByStatusCalled() {
        List<Pizza3> pzList = new ArrayList<>();
        Pizza3 pz1 = new Pizza3();
        pz1.setStatus(Pizza3.PizzaStatusEnum.DELIVERED);

        Pizza3 pz2 = new Pizza3();
        pz2.setStatus(Pizza3.PizzaStatusEnum.ORDERED);

        Pizza3 pz3 = new Pizza3();
        pz3.setStatus(Pizza3.PizzaStatusEnum.ORDERED);

        Pizza3 pz4 = new Pizza3();
        pz4.setStatus(Pizza3.PizzaStatusEnum.READY);

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

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

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

7.1. 单身模式

通常,使用Singleton模式实现一个类是非常重要的。枚举提供了一种简单快捷的单例实现方式。

除此之外,由于enum类在实现Serializable接口,因此JVM保证类是单例,这与传统实现不同,我们必须确保在反序列化期间不创建新实例。

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

public enum PizzaDeliverySystemConfiguration {
    INSTANCE;

    PizzaDeliverySystemConfiguration() {
       // 初始化
    }

    private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;

    public static PizzaDeliverySystemConfiguration getInstance() {
        return INSTANCE;
    }

    public PizzaDeliveryStrategy getDeliveryStrategy() {
        return deliveryStrategy;
    }
}

7.4 策略模式

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

添加新策略意味着添加新的实现类。

使用枚举,这可以通过更少的工作来实现,添加新的实现意味着仅通过一些实现来定义另一个实例。

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

public enum PizzaDeliveryStrategy {
    EXPRESS {
        @Override
        public void deliver(Pizza3 pz) {
            System.out.println("快速到达");
        }
    },
    NORMAL {
        @Override
        public void deliver(Pizza3 pz) {
            System.out.println("普通到达");
        }
    };

    public abstract void deliver(Pizza3 pz);
}

将以下方法添加到Pizza3

 public void deliver() {
        if (isDeliverable()) {
            PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy().deliver(this);
            this.setStatus(PizzaStatusEnum.DELIVERED);
        }
    }

测试代码

    @Test
    public void test_Delivered() {
        Pizza3 pz = new Pizza3();
        pz.setStatus(Pizza3.PizzaStatusEnum.READY);
        pz.deliver();
        assertTrue(pz.getStatus() == Pizza3.PizzaStatusEnum.DELIVERED);
    }

8 .结论

在本文中,我们探讨了Java enum,从语言基础知识到更高级和有趣的实际用例。

最后,往常一样,代码可以在Github上找到。