本文我们学习java的枚举类型,其解决什么问题以及如何在实践中使用一些设计模式。在java5引入enum关键字,表示一种特殊类型的类,其总是继承java.lang.Enum类,读者可以查看其官方文档。
常量使用枚举定义使代码可读性增强,实现编译时检查,可接受值列表前面编写文档,避免因传入无效值导致的异常行为。
下面示例定义一个简单的枚举类型 pizza订单的状态,共有三种 ORDERED, READY, DELIVERED状态:
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
另外,枚举类型自带一些有用的方法,如果通过static final定义常量,这些方法需要额外添加。
到目前为止,我们已经对枚举有基本的认识,枚举是什么,如何使用枚举。下面我们给上面的示例进行升级,给枚举增加一些额外的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.
}
既然枚举类型能确保在jvm中只有一个常量实例存在,我们可以放心地使用 ==
操作符比较两个枚举变量(如上面实例),而且==操作符提供了编译时和运行时的安全性。
首先我们看运行时安全性,在下面的代码片段中使用==操作符比较状态,如果值为null不会抛NullPointerException异常,相反如果使用equals方法会抛NullPointerException异常:
Pizza testPz = new Pizza();
if (testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)) {
// will throw exception
};
if (testPz.getStatus() == Pizza.PizzaStatus.DELIVERED) {
// ok
};
对于编译时安全性,我们看另一个示例,两个不同枚举类型进行比较,使用equal方法比较结果确定为true,因为getStatus方法的枚举值与另一个类型枚举值一致,但逻辑上应该为false。这个问题可以使用==操作符避免。因为编译器会表示类型不兼容错误:
if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);
枚举类型可以在switch语句中使用:
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return 5;
case READY: return 2;
case DELIVERED: return 0;
}
return 0;
}
我们可以给枚举类型定义构造函数、属性和方法,使其功能更强大。现在我们扩展上面的示例,实现pizza订单状态从一个阶段过渡到另一个阶段,看看如何删除前面使用的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 void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
return true;
}
return false;
}
public void setStatus(PizzaStatus status){
this.status = status;
}
public PizzaStatus getStatus(){
return status;
}
}
下面情况测试代码:
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
EnumSet是Set接口的特殊实现,仅用于枚举类型。由于使用了内部位向量表示,与HashSet相比,它是一组特定Enum常量的非常有效和紧凑的表示。它还提供了一种类型安全的方法来替代传统的基于int的“位标志”,从而允许我们编写更易于阅读和维护的简洁代码。
EnumSet 是抽象类,其有两个实现:RegularEnumSet 、JumboEnumSet,选择哪一个取决于实例化时枚举中常量的数量。
在很多场景中的枚举常量集合操作(如:取子集、增加、删除、containsAll和removeAll批操作)使用EnumSet非常合适;如果需要迭代所有可能的常量则使用Enum.values()。
下面代码展示如何使用EnumSet创建常量子集:
public class Pizza {
private static EnumSet 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 getAllUndeliveredPizzas(List 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 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 undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
EnumMap是一个专门化的映射实现,用于将枚举常量用作键。与对应的HashMap相比,它是一个高效紧凑的实现,并且在内部表示为一个数组:
EnumMap map;
下面看一个实际示例展示如何使用:
public static EnumMap>
groupPizzaByStatus(List pizzaList) {
EnumMap> pzByStatus =
new EnumMap>(PizzaStatus.class);
for (Pizza pz : pizzaList) {
PizzaStatus status = pz.getStatus();
if (pzByStatus.containsKey(status)) {
pzByStatus.get(status).add(pz);
} else {
List newPzList = new ArrayList();
newPzList.add(pz);
pzByStatus.put(status, newPzList);
}
}
return pzByStatus;
}
执行下面测试演示Map接口实现EnumMap的强大能力:
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List 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> 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);
}
通常使用类实现单例模式非常繁琐,枚举提供了一个非常简单且快速的方式实现单例模式。因为枚举类底层实现了Serializable 接口,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;
}
}
策略模式一般情况通过一个接口和不同实现类方式实现。增加新的策略意味着增加新的实现类;使用枚举实现更简单,增加新的实现意味着仅定义另一个带相关实现的实例。请看策略模式的代码:
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);
}
Pizza 类使用java 8 特性重写,可以看到使用lambda表达式和流API使getAllUndeliveredPizzas() 和 groupPizzaByStatus() 方法更简洁:
public static List getAllUndeliveredPizzas(List input) {
return input.stream().filter(
(s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public static EnumMap>
groupPizzaByStatus(List pzList) {
EnumMap> map = pzList.stream().collect(
Collectors.groupingBy(Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
return map;
}
使用json库,可能有json表示枚举类型,因为其为pojo。下面代码显示jackson注解实现序列化:
@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 和 PizzaStatus 进行测试:
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
}
本文我们讨论了java枚举类型,从基础知识到高级应用以及实际应用场景,让我们感受到枚举的强大功能。