几乎没啥用的示例 初见枚举,见过最多的实例无外乎有两个 Color 和 Week,拿其中一个举个例子看看
1 2 3 4 5 public enum Color { GREEN, YELLOW, RED }
然后呢?怎么用?if/else ? switch ?
这样用的话,跟常量类唯一的区别就是限定了类型,让编译器能够发力限制使用以此枚举为入参的 api 参数类型,在一定程度上是一种进步,但实际使用完全没有变化。
初级进化版本 我们用的数据通常是要持久化的,持久化一般用数字或者某些字符串来代替其实际含义,这样来看上面那个枚举几乎没啥用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Getter @AllArgsConstructor public enum Color { GREEN(1 ), YELLOW(2 ), RED(3 ); private Integer code; }
现在持久化的数据和枚举建立联系了,咋个用呢?貌似还是 switch 只是多了一个 getCode() 的方法能拿到对应持久化的值,对于要通过枚举值获得持久化的值,到这里就可以做到了。
可是我们从前端拿到的值是持久化的代表值,咋个转化成枚举呢?也就是从 code -> enum 这步该怎么搞呢?
再进化版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Getter @AllArgsConstructor public enum Color { GREEN(1 ), YELLOW(2 ), RED(3 ); private Integer code; private static final Map<Integer, Color> CODE_TO_ENUM = Arrays.stream(Color.values()) .collect(Collectors.toMap(Color::getCode, e -> e)); public static Optional<Color> forCode (Integer code) { return Optional.ofNullable(CODE_TO_ENUM.get(code)); } }
到这里,这个枚举就比较能覆盖常见使用场景了,比如,前台选中某种颜色,传到后端
后端的 xxxService.process(Color color) 只接受 Color 类型,在这一层就限制了入参的范围,让 API 更健壮。
1 2 3 4 5 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() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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; private static final Map<Integer, Color> CODE_TO_ENUM = Arrays.stream(Color.values()) .collect(Collectors.toMap(Color::getCode, e -> e)); public static Optional<Color> forCode (Integer code) { return Optional.ofNullable(CODE_TO_ENUM.get(code)); } public abstract void printColor () ; }
先来看下原始的 switch 没有用抽象方法的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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() 的动作
1 2 3 4 5 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 更加简单高效。