枚举系列文章目录:
【Enum】详解 Java 中的枚举类(上)
【Enum】详解 Java 中的枚举类(下)
【Enum】枚举在 Java 中的常用用法
相信大家通过前面几篇关于“枚举”的博文的学习,大大地提升了对枚举的认识。仅仅地知道枚举的原理不行啊,还得知道怎么去使用它。那么,下面就简单地介绍下枚举的常用用法吧~~
场景:
页面显示订单列表,且每个订单都有它自己的状态。其订单状态包括:1:未付款;2:已完成;3:待评价。现在需要根据订单状态的数值显示对应的状态(中文)。因为数据库里面存储的值是数值型的。所以,在页面需要转换为对应的中文。
之前,我们一般这样做:在常量类/接口里面定义对应的常量:
public class OrderConstant {
// 未付款
public static final Integer NO_PAID = 1;
// 已完成
public static final Integer HAS_FINISHED = 2;
// 待评价
public static final Integer TO_EVALUATE = 3;
public static final String MSG_NO_PAID = "未付款";
public static final String MSG_HAS_FINISHED = "已完成";
public static final String MSG_TO_EVALUATE = "待评价";
}
然后,在业务逻辑层里面查询订单列表:
public class OrderService {
private List<TabOrder> orderList = new ArrayList<>();
public void init() {
orderList.add(new TabOrder("10001", "u1", OrderConstant.NO_PAID));
orderList.add(new TabOrder("10002", "u1", OrderConstant.HAS_FINISHED));
orderList.add(new TabOrder("10003", "u2", OrderConstant.NO_PAID));
orderList.add(new TabOrder("10004", "u3", OrderConstant.TO_EVALUATE));
orderList.add(new TabOrder("10005", "u3", OrderConstant.HAS_FINISHED));
}
// 获取所有订单
public List<OrderVo> listOrders() {
// 初始化订单
init();
List<TabOrder> orderList = getOrderList();
List<OrderVo> orderVos = new ArrayList<>();
if (null == orderList || orderList.size() < 0) {
return orderVos;
}
for (TabOrder tabOrder : orderList) {
OrderVo orderVo = new OrderVo();
orderVo.setsId(tabOrder.getsId());
orderVo.setUserId(tabOrder.getUserId());
if (tabOrder.getiStatus().equals(OrderConstant.NO_PAID)) {
orderVo.setStatus(OrderConstant.MSG_NO_PAID);
} else if (tabOrder.getiStatus().equals(OrderConstant.HAS_FINISHED)) {
orderVo.setStatus(OrderConstant.MSG_HAS_FINISHED);
} else if (tabOrder.getiStatus().equals(OrderConstant.TO_EVALUATE)) {
orderVo.setStatus(OrderConstant.MSG_TO_EVALUATE);
}
orderVos.add(orderVo);
}
return orderVos;
}
public List<TabOrder> getOrderList() {
return orderList;
}
}
上述代码:查询出所有订单列表,然后存储在 TabOrder 类集合中,然后转换为 OrderVo 类集合,返回给前端。
获取到订单状态后,会进行状态转化:
for (TabOrder tabOrder : orderList) {
OrderVo orderVo = new OrderVo();
orderVo.setsId(tabOrder.getsId());
orderVo.setUserId(tabOrder.getUserId());
// 订单状态转换
if (tabOrder.getiStatus().equals(OrderConstant.NO_PAID)) {
orderVo.setStatus(OrderConstant.MSG_NO_PAID);
} else if (tabOrder.getiStatus().equals(OrderConstant.HAS_FINISHED)) {
orderVo.setStatus(OrderConstant.MSG_HAS_FINISHED);
} else if (tabOrder.getiStatus().equals(OrderConstant.TO_EVALUATE)) {
orderVo.setStatus(OrderConstant.MSG_TO_EVALUATE);
}
orderVos.add(orderVo);
}
对应数据库的字段的实体类:
public class TabOrder {
// 主键
private String sId;
// 用户id
private String userId;
// 订单状态
private Integer iStatus;
// getter/setter/toString
}
返回给前端的 Vo 类:
public class OrderVo {
private String sId;
private String userId;
private String status;
// getter/setter/toString
}
TabOrder
和 OrderVo
的区别:TabOrder 中的订单状态使用数值型表示,而 OrderVo 类中的订单状态使用中文表示。
那么,上述使用常量的缺点:代码臃肿、不易维护。常量类中既记录了订单状态数值,又记录了订单状态中文描述;在业务逻辑层中还要对订单状态进行判断,然后才能转换为对应的中文描述。如果新增了一个订单状态,那么,改动就稍微有点大了。不仅要改动常量类,还要改动业务逻辑层。
那么,使用了枚举类之后呢?
首先,增加一个订单状态枚举类:
public enum OrderStatusEnum {
UNKNOW(0, "未知")
,
NO_PAID(1, "未付款")
,
HAS_FINISHED(2, "已完成")
,
TO_EVALUATE(3, "待评价")
;
private Integer status;
private String desc;
OrderStatusEnum(Integer status, String desc) {
this.status = status;
this.desc = desc;
}
// 根据订单状态获取订单枚举类型
public static OrderStatusEnum getOrderStatusEnumByStatus(Integer status) {
OrderStatusEnum[] values = OrderStatusEnum.values();
if (null == values || values.length < 1) {
return OrderStatusEnum.UNKNOW;
}
for (OrderStatusEnum value : values) {
if (value.getStatus().equals(status)) {
return value;
}
}
return OrderStatusEnum.UNKNOW;
}
// getter
}
然后,修改业务逻辑层代码(只修改 for 循环语句):
for (TabOrder tabOrder : orderList) {
OrderVo orderVo = new OrderVo();
orderVo.setsId(tabOrder.getsId());
orderVo.setUserId(tabOrder.getUserId());
OrderStatusEnum orderStatusEnum = OrderStatusEnum.getOrderStatusEnumByStatus(tabOrder.getiStatus());
orderVo.setStatus(orderStatusEnum.getDesc());
orderVos.add(orderVo);
}
这样,依旧可以达到相同的效果。可见,使用枚举类型确实可以简化代码开发。
但是,并不是所有的常量都可以使用枚举进行替换!!只是针对某一类场景确实是至关重要的:常量之间存在关联关系。如:订单状态的数值型与中文描述。此时,使用枚举能大大简化我们的工作
那么,为什么不可以用枚举替换所有的常量呢?枚举用起来多方便啊
在 Java 官方文档中有提到,不建议使用枚举:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
大致意思:使用枚举会比使用静态变量多消耗两倍的内存。
为什么枚举会占内存?
简单来说:通过反编译后,枚举类型转化为一个类,会帮我们生成两个属性(name、ordinal)、枚举实例、声明了一个枚举对象的数组。而使用常量,只会占用数据类型对应的字节 * 常量个数。这样一对比,枚举占用内存的大小比静态变量多得多。Java 枚举(enum) 详解7种常见的用法 原理 枚举占用内存的原因。
所以,最后到底用不用枚举?在实际开发中,还需要根据实际使用场景去斟酌,杜绝滥用。
JDK1.6 之前的 switch 语句只支持 int、char、
JDK1.6,switch 语句支持 enum 类型。使用枚举,能让我们的代码可读性更强。
public enum SeasonEnum {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
public class TestEnum {
// 判断是否是四季
public void judgeSeason(SeasonEnum seasonEnum) {
switch (seasonEnum) {
case SPRING:
System.out.println("这是春天");
break;
case SUMMER:
System.out.println("这是夏天");
break;
case AUTUMN:
System.out.println("这是秋天");
break;
case WINTER:
System.out.println("这是冬天");
break;
default:
System.out.println("这是错误的季节");
}
}
public static void main(String[] args) {
TestEnum testEnum = new TestEnum();
SeasonEnum spring = SeasonEnum.SPRING;
testEnum.judgeSeason(spring);
}
}
定义一个含有抽象方法的枚举类:
public enum OrderEnum {
// 未付款
NO_PAID(1) {
@Override
public String getOrderStatus() {
return "未付款";
}
}
,
// 已完成
HAS_FINISHED(2) {
@Override
public String getOrderStatus() {
return "已完成";
}
}
,
// 待评价
TO_EVALUATE(3) {
@Override
public String getOrderStatus() {
return "待评价";
}
};
private Integer code;
OrderEnum(Integer code) {
this.code = code;
}
public abstract String getOrderStatus();
}
测试:
public class Test {
public static void main(String[] args) {
OrderEnum orderEnum = OrderEnum.NO_PAID;
String orderStatus = orderEnum.getOrderStatus();
System.out.println(orderStatus);
orderEnum = OrderEnum.HAS_FINISHED;
orderStatus = orderEnum.getOrderStatus();
System.out.println(orderStatus);
}
}
定义一个公共的接口,用来返回枚举中的属性的值
public interface IErrorCode<K, V, T extends Enum> {
// 返回一个枚举
T get();
// 返回状态码
K getCode();
// 返回信息
V getMsg();
}
定义一个状态码的枚举类
public enum CodeStatusEnum implements IErrorCode<Integer, String, CodeStatusEnum> {
EMPTY_PARAM(1001, "参数为空")
,
ERROR_PARAM(1002, "参数错误")
,
EMPTY_OBJECT(1003, "对象为空")
;
private Integer code;
private String msg;
CodeStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public CodeStatusEnum get() {
return this;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
}
定义一个订单状态的枚举类
public enum OrderStatusEnum implements IErrorCode<Integer, String, CodeStatusEnum> {
UNKNOW(0, "未知")
,
NO_PAID(1, "未付款")
,
HAS_FINISHED(2, "已完成")
,
TO_EVALUATE(3, "待评价")
;
private Integer status;
private String desc;
OrderStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public CodeStatusEnum get() {
return this;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
}
测试:
public class Test {
public static void test(IErrorCode iErrorCode) {
if (iErrorCode.get() == CodeStatusEnum.EMPTY_OBJECT) {
System.out.println(MessageFormat.format("key: {0}, value: {1}", iErrorCode.getCode(), iErrorCode.getMsg()));
}
}
public static void main(String[] args) {
IErrorCode<Integer, String, CodeStatusEnum> iErrorCode = CodeStatusEnum.EMPTY_OBJECT;
test(iErrorCode);
}
}
总结:项目中使用同一接口管理枚举类, 在方法参数中使用接口而不是用具体的枚举对象作为入参, 可以一定程度上降低程序的耦合性
另外一种用法:提取一个公共的方法,通过code获取其msg
public class EnumUtil {
public static <T extends ICode> T getByCode(Integer code, Class<T> enumClass) {
if (code != null) {
for (T each : enumClass.getEnumConstants()) {
if (code.equals(each.getCode())) {
return each;
}
}
}
throw new RuntimeException("没有可匹配的code:" + code);
}
}
interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
}
public class InterfaceOrganizeEnum {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
}
}
暂不知道这个用法的使用场景。
===========================================================================================
2021-12-09 更:
优化前:
Integer personType = 2;
if (-1 == personType) {
// TODO
} else if (1 == personType) {
// TODO
} else if (2 == personType) {
// TODO
}
优化后:
public enum PersonEnum {
ERROR(-1) {
@Override
public void doSomething() {
System.out.println("ERROR");
}
}
,
CHILDREN(1) {
@Override
public void doSomething() {
System.out.println("哭哭哭");
}
}
,
ADULT(2) {
@Override
public void doSomething() {
System.out.println("挣挣挣");
}
}
,
OLD(3) {
@Override
public void doSomething() {
System.out.println("走走走");
}
}
;
public static PersonEnum getPersonEnumByType(Integer iPersonType) {
PersonEnum[] values = PersonEnum.values();
if (null == values || values.length == 0) {
return PersonEnum.ERROR;
}
for (PersonEnum personEnum : values) {
if (personEnum.getiPersonType().equals(iPersonType)) {
return personEnum;
}
}
return PersonEnum.ERROR;
}
private Integer iPersonType;
PersonEnum(Integer iPersonType) {
this.iPersonType = iPersonType;
}
// 抽象方法
public abstract void doSomething();
public Integer getiPersonType() {
return iPersonType;
}
}
测试:
public class PersonEnumTest {
public static void main(String[] args) {
Integer personType = -1;
PersonEnum.getPersonEnumByType(personType).doSomething();
}
}