几乎没啥用的示例
初见枚举,见过最多的实例无外乎有两个 Color
和 Week
,拿其中一个举个例子看看
public enum Color {
GREEN,
YELLOW,
RED
}
然后呢?怎么用?if/else
? switch
?
这样用的话,跟常量类唯一的区别就是限定了类型,让编译器能够发力限制使用以此枚举为入参的 api
参数类型,在一定程度上是一种进步,但实际使用完全没有变化。
初级进化版本
我们用的数据通常是要持久化的,持久化一般用数字或者某些字符串来代替其实际含义,这样来看上面那个枚举几乎没啥用了。
// 使用了 lombok 的注解,简化代码
@Getter
@AllArgsConstructor
public enum Color {
GREEN(1),
YELLOW(2),
RED(3);
/**
* 持久化的 code 值
*/
private Integer code;
}
现在持久化的数据和枚举建立联系了,咋个用呢?貌似还是 switch
只是多了一个 getCode()
的方法能拿到对应持久化的值,对于要通过枚举值获得持久化的值,到这里就可以做到了。
可是我们从前端拿到的值是持久化的代表值,咋个转化成枚举呢?也就是从 code
-> enum
这步该怎么搞呢?
再进化版本
// 使用了 lombok 的注解,简化代码
@Getter
@AllArgsConstructor
public enum Color {
GREEN(1),
YELLOW(2),
RED(3);
private Integer code;
/**
* 表驱动
* CODE: 枚举实例
*
* 不要直接暴露这个 Map 出去,提供一个方法出去,限制对 Map 的操作
*/
private static final Map<Integer, Color> CODE_TO_ENUM =
Arrays.stream(Color.values())
.collect(Collectors.toMap(Color::getCode, e -> e));
/**
* 通过 code 获取枚举实例
* 这里使用了 Optional 语义上表示如果 code 不存在(非法值),起一个提示作用
*
* @param code code
* @return 枚举实例
*/
public static Optional<Color> forCode(Integer code) {
return Optional.ofNullable(CODE_TO_ENUM.get(code));
}
}
到这里,这个枚举就比较能覆盖常见使用场景了,比如,前台选中某种颜色,传到后端
后端的 xxxService.process(Color color)
只接受 Color
类型,在这一层就限制了入参的范围,让 API 更健壮。
Optional<Color> colorOpt = Color.forCode(colorCode);
if (colorOpt.isPresent()) {
return xxxService.process(colorOpt.get());
}
return "something else";
终极进化
一般上面的版本就够用了,但是如果后面对于不同的 Color
要做除了 getCode()
以外的操作,还是逃不脱 if/else
和 switch
这两种做法,那该怎么解决呢?答案是抽象方法。
是的,在枚举里是可以定义抽象方法的,各个实例去实现这个方法就可以了,代码可以参考 JDK8 的 java.util.concurrent.TimeUnit#toXxx()
方法
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public enum Color {
GREEN(1) {
@Override
public void printColor() {
System.out.println("绿色");
}
},
YELLOW(2) {
@Override
public void printColor() {
System.out.println("黄色");
}
},
RED(3) {
@Override
public void printColor() {
System.out.println("红色");
}
};
private Integer code;
/**
* CODE: 枚举实例
*/
private static final Map<Integer, Color> CODE_TO_ENUM =
Arrays.stream(Color.values())
.collect(Collectors.toMap(Color::getCode, e -> e));
/**
* 通过 code 获取枚举实例
*
* 这里使用了 Optional 语义上表示如果 code 不存在(非法值),起一个提示作用
*
* @param code code
* @return 枚举实例
*/
public static Optional<Color> forCode(Integer code) {
return Optional.ofNullable(CODE_TO_ENUM.get(code));
}
/**
* 打印颜色
*/
public abstract void printColor();
}
先来看下原始的 switch
没有用抽象方法的使用方式
Optional<Color> colorOpt = forCode(code);
if (colorOpt.isPresent()) {
Color color = colorOpt.get();
switch (color) {
case GREEN:
System.out.println("绿色");
break;
case YELLOW:
System.out.println("黄色");
break;
case RED:
System.out.println("红色");
break;
default:
break;
}
}
使用抽象方法,如果要做 printColor()
的动作
Optional<Color> colorOpt = Color.forCode(colorCode);
if (colorOpt.isPresent()) {
// 这里体现了多态,根据具体实例调用具体方法
colorOpt.get().printColor();
}
可以看到,逻辑上是一样的,使用抽象方法的这种 主逻辑
更清晰了,做了什么动作一目了然,没有被淹没在各种不同情况的实现细节上面,如果开始只有一个动作用 switch
也没啥,但要是对于不同的枚举值有很多不同的动作,那每做一个动作都去 switch
就真的要人命了。
要注意一点的是,代码是一行也没有少写的,该有的逻辑都有,只有部分交给了程序自己去判断。
总结
- 内部以
code
对应枚举实例的Map
为表,提供通过 code 获取实例的方法; - 定义抽象方法,实例去实现抽象方法,利用多态做分支判断。
预留问题:现在的 code
是一个确定值,从 Map
往外拿值有就是有,没有就是没有,那如果 code
是范围值呢?就像考试成绩的 A,B,C,D 它们是区间分段的,怎么搞呢?
见下一篇 用枚举值表示范围。
附
- 枚举天然单例,可以放心大胆的用
==
进行比较。 - 和枚举配套使用的 EnumSet/EnumMap 更加简单高效。
【LIXI.FUN - 枚举的常用姿势 (JAVA 为例)】