枚举
在本教程中,我们将了解什么是 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
// SUNDAY
4. 使用“==”比较枚举
由于枚举类型确保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.
}
测试上面的代码。
@Test
public 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 weekends = EnumSet.of(WeekDay.SATURDAY, WeekDay.SUNDAY);
for (WeekDay weekend : weekends) {
System.out.printf("今天是周末:%s\n", weekend.name());
}
List weekDays = Arrays.stream(WeekDay.values()).filter(WeekDay::isWeekends).toList();
System.out.println(weekDays.size() == weekends.size());
7.2 EnumMap
EnumMap 是一种专门的 Map 实现,旨在与枚举常量一起用作键。与其对应的 HashMap 相比,它是一种高效且紧凑的实现,在内部表示为数组。
定义的时候需要指定keyType。
EnumMap 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。
结语
参考链接:
- https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
- https://www.baeldung.com/a-guide-to-java-enums