枚举
在本教程中,我们将了解什么是 Java 枚举、它们解决的问题以及它们的一些设计模式如何在实践中使用。
1. 概述
Java 5 首先引入了 enum 关键字。它表示一种特殊类型的类,它总是扩展 java.lang.Enum 类。有关使用的官方文档,我们可以转到文档。 以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而导致的意外行为。
2. 定义
最简单的定义一个枚举类。
public enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}3. 自定义枚举方法
下面这个方法,会根据参数local进行判断,如果是"CN"返回值就加1,如果不是就直接返回序数。
int daysOrderInWeek(String local) { if (local.equals("CN")) { return ordinal() + 1; } return ordinal();}public static void main(String[] args) { System.out.println(WeekDay.MONDAY.daysOrderInWeek("CN")); System.out.println(WeekDay.MONDAY.daysOrderInWeek("EN"));}// 结果// 1// 0可以看到上面的方法是使用枚举中定义的值来访问,如果需要定义枚举类访问的方法,要用static修饰,定义一个静态方法。比如:
static WeekDay firstDayInWeek(String local) { if (local.equals("CN")) { return MONDAY; } return SUNDAY;}// 测试System.out.println(WeekDay.firstDayInWeek("CN"));System.out.println(WeekDay.firstDayInWeek("US"));// 结果// MONDAY// SUNDAY4. 使用“==”比较枚举
由于枚举类型确保JVM中只有一个常量实例,因此我们可以安全地使用“==”运算符来比较两个变量。此外,“==”运算符提供编译时和运行时安全性。
WeekDay firstDayUS = WeekDay.firstDayInWeek("US");if (firstDayUS == WeekDay.SUNDAY) { System.out.println("对的,就是这样。");}5. 在switch中使用
非常方便和简单。
public void tellItLikeItIs() { switch (day) { case MONDAY -> System.out.println("周一很糟糕。"); case FRIDAY -> System.out.println("周五非常好。"); case SATURDAY, SUNDAY -> System.out.println("周末是最好的。"); default -> System.out.println("周中的日子一般般。"); }}6. 枚举的字段、方法以及构造函数
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.}测试上面的代码。
@Testpublic void givenPizaOrder_whenReady_thenDeliverable() { Pizza testPz = new Pizza(); testPz.setStatus(Pizza.PizzaStatus.READY); assertTrue(testPz.isDeliverable());}7. EnumSet和EnunMap
7.1 EnumSet
EnumSet 是专门用于 Enum 类型的 Set 实现。
EnumSet<WeekDay> weekends = EnumSet.of(WeekDay.SATURDAY, WeekDay.SUNDAY);for (WeekDay weekend : weekends) { System.out.printf("今天是周末:%s\n", weekend.name());}List<WeekDay> weekDays = Arrays.stream(WeekDay.values()).filter(WeekDay::isWeekends).toList();System.out.println(weekDays.size() == weekends.size());7.2 EnumMap
EnumMap 是一种专门的 Map 实现,旨在与枚举常量一起用作键。与其对应的 HashMap 相比,它是一种高效且紧凑的实现,在内部表示为数组。
定义的时候需要指定keyType。
EnumMap<WeekDay, String> nameMap = new EnumMap<>(WeekDay.class);nameMap.put(WeekDay.MONDAY, "周一");nameMap.put(WeekDay.TUESDAY, "周二");nameMap.put(WeekDay.WEDNESDAY, "周三");nameMap.put(WeekDay.THURSDAY, "周四");System.out.println(nameMap.get(WeekDay.MONDAY));8. 使用枚举实现单例模式
通常,使用单例模式实现一个类是非常重要的。枚举提供了一种实现单例的快速简便的方法。
此外,由于枚举类在底层实现了 Serializable 接口,因此 JVM 保证该类是单例。这与传统实现不同,在传统实现中,我们必须确保在反序列化期间不创建新实例。
public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance() { return INSTANCE; } private final WeekDay weekDay = WeekDay.SUNDAY; public String sundayName() { return weekDay.name(); }}9. 使用枚举实现策略模式
通常,策略模式是通过具有由不同类实现的接口来编写的。 添加新策略意味着添加新的实现类。
使用枚举,我们可以更轻松地实现这一目标,添加新的实现意味着只需定义另一个具有某种实现的实例。
public enum CalculatorEnum { ADD("+") { @Override public int exec(int a, int b) { return a + b; } }, SUB("-") { @Override public int exec(int a, int b) { return a - b; } }, MUL("*") { @Override public int exec(int a, int b) { return a * b; } }; private final String value; CalculatorEnum(String value) { this.value = value; } public String getValue() { return this.value; } public abstract int exec(int a, int b);}测试一下运行结果:
public class StrategyTest { public static void main(String[] args) { int a = 2; int b = 3; System.out.println("两数相加等于:" + exec("+", a, b)); System.out.println("两数相减等于:" + exec("-", a, b)); System.out.println("两数相乘等于:" + exec("*", a, b)); } private static int exec(String symbol, int a, int b) { if (symbol.equals(CalculatorEnum.ADD.getValue())) { return CalculatorEnum.ADD.exec(a, b); } else if (symbol.equals(CalculatorEnum.SUB.getValue())) { return CalculatorEnum.SUB.exec(a, b); } else if (symbol.equals(CalculatorEnum.MUL.getValue())) { return CalculatorEnum.MUL.exec(a, b); } return -1; }}策略枚举是一个非常优秀和方便的模式,但是它受到枚举类型的限制,每个枚举项都是public、final、static的,扩展性收到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。——《设计模式之禅》
10. 枚举的JSON表示
使用 Jackson 库,可以使用 JSON 表示枚举类型,就好像它们是 POJO 一样。
// 使用注释 修饰枚举类@JsonFormat(shape = JsonFormat.Shape.OBJECT)// 修饰字段 @JsonProperty("fieldName")有关枚举类型的 JSON 序列化/反序列化(包括自定义)的更多信息,我们可以参考 Jackson。