1. 概述
本文将深入探讨Java枚举(enum)的核心概念、解决的问题以及在实际开发中的设计模式应用。
Java 5首次引入了enum
关键字。它表示一种特殊的类,默认继承自java.lang.Enum
类。官方使用文档可参考Java 21 API文档。
使用枚举定义常量能带来以下优势:
- 提升代码可读性
- 实现编译时类型检查
- 提前声明合法值列表
- 避免因传入无效值导致的意外行为
下面是一个简单的披萨订单状态枚举示例,包含三种状态:已下单(ORDERED)、已就绪(READY)、已送达(DELIVERED):
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
此外,枚举自带许多实用方法,若使用传统的public static final
常量则需要自行实现这些方法。
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;
}
// 状态变量的getter和setter方法
}
3. 使用"=="比较枚举类型
由于JVM中每个枚举常量只有唯一实例,我们可以安全使用==
运算符进行比较(如上例所示)。该运算符还提供编译时和运行时双重安全保障。
运行时安全
以下代码片段展示了==
与equals
的区别:
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); // 可能抛NullPointerException
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); // 安全处理null值
编译时安全
考虑不同枚举类型的比较场景:
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 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());
}
// 状态变量的getter和setter方法
}
测试代码验证其工作原理:
@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
实现。相比HashSet
,它采用内部位向量表示,具有以下优势:
- 极高的存储和计算效率
- 提供类型安全的替代方案,替代传统的int位标志
- 代码更简洁可读
EnumSet
是抽象类,根据枚举常量数量自动选择RegularEnumSet
或JumboEnumSet
实现。
使用场景建议:
- 需要操作枚举常量集合时(子集操作、增删、批量操作等)优先使用
EnumSet
- 仅需遍历所有常量时使用
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);
}
}
// 状态变量的getter和setter方法
}
测试代码验证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
是专为枚举常量作为键的Map
实现。相比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;
}
测试代码验证分组功能:
@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 单例模式
传统单例实现较为繁琐,而枚举提供了一种简单粗暴的实现方式。更重要的是:
- 枚举类隐式实现
Serializable
接口 - JVM保证枚举单例的唯一性(避免反序列化创建新实例的坑)
单例实现示例:
public enum PizzaDeliverySystemConfiguration {
INSTANCE;
PizzaDeliverySystemConfiguration() {
// 初始化配置(如覆盖默认配送策略)
}
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与枚举
利用Java 8的lambda和Stream API,可大幅简化Pizza
类中的方法:
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表示
使用Jackson库可将枚举序列化为JSON对象(类似POJO)。添加@JsonFormat
注解实现:
@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
}
10. 总结
本文系统探讨了Java枚举的核心特性,从基础语法到高级实战应用,涵盖:
- 枚举类型的基础优势
- 自定义方法和构造器
- 高效比较操作
- 与
EnumSet
/EnumMap
的集成 - 设计模式实现(单例/策略)
- Java 8新特性应用
- JSON序列化方案
文中代码示例可在Github仓库获取。掌握这些技巧,能让你的代码更简洁、类型更安全、性能更高效!