关于枚举的高级用法

枚举回顾

Java 中的枚举,大家都不会陌生,他的一些特点,能让我们得心应手的实现业务逻辑。

  • 它的单例特性,我们就可以直接使用 == 来做逻辑校验,如:
1
2
3
4
5
6
7
public enum Gender {
MAN, WOMAN;
}
if (Gender.MAN == gender) {
// do something
}

  • 也可以直接实现一个线程单例,JVM 层面提供的保障
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SingleEnumDemo {
public enum SingleEn {

INSTANCE;

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

public static void main(String[] args) {
SingleEn.INSTANCE.setName("zp");
System.out.println(SingleEn.INSTANCE.getName());
}
}
  • 还能校验参数是否非法,使用 valueOf 方法
1
2
3
4
5
6
7
public enum Gender {
MAN, WOMAN;
}

if (null == Gender.valueOf("xxxxx")) {
throw new IllegalArgumentException();
}
  • 当然最常见的,还是定义各种状态,似乎觉得枚举天生就是这样用的
1
2
3
4
5
6
7
8
9
10
11
@Getter
@AllArgsConstructor
public enum ShareRuleEnum {
REDPACKET("店鋪紅包", 1),
MANJIAN("滿減優惠", 2),
MANZENG("滿贈活動", 4),
ZHEKOU("折扣商品", 8),
;
private String shareRuleName;
private Integer code;
}

枚举使用的其他案例

某个场景,需要一个查询的接口,查询的参数有时间区间,即开始时间~结束时间。自然我们会想到,定义两个请求参数 startend

1
2
3
4
5
public MsgResult<XXXResult> get(
@RequestParam Date start, @RequestParam Date end
) {
// do something
}

在这个场景上,附加上了一个规则,只能使用给定的时间区间(如:今天,昨天,本月,上月…),这时大家应该能想到枚举又有用武之地了,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public MsgResult<XXXResult> get(
@RequestParam DateRangeEnum dateRange
) {
Date start, end;
if (dateRange == TODAY) {
start = ..., end = ...;
}
...
// do something
}

enum DateRangeEnum {
TODAY, YESTEDAY, CURRENT_MONTH, LAST_MONTH;
}

等等……

思考一下,我们为什么要定义枚举?
是为了要约束入参的范围,这个枚举确实做到了部分,因为只能传递 TODAYYESTEDAY, CURRENT_MONTH, LAST_MONTH

但这些枚举值所代表的含义是什么呢?例如 TODAY 是希望 start 为今天的开始,end 为明天的开始(闭区间)

然而这些代码却没有在 DateRangeEnum 类里面体现,而写在了其他方法里面。

由此我们得出结论,我们提供的 DateRangeEnum 类表达的意思不全的,是残缺的,最起码现在看来残缺了时间区间的表达。

改进一版,提供一个时间区间的接口(面向接口编程),以及一个普通 Bean 的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface DateRange {
Date getStart();
Date getEnd();
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class DateRangeParam implements DateRange {
private Date start;
private Date end;
}

改造枚举 DateRangeEnum, 让他能够产生时间区间,我们就利用 Java8 已经定义好了接口 Supplier,让 DateRangeEnum 实现该接口。

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
public MsgResult<XXXResult> get(
@RequestParam DateRangeEnum dateRange
) {
DateRange range = dateRange.get();
range.getStart();
range.getEnd();
...
// do something
}


enum DateRangeEnum implements Supplier<DateRange> {
TODAY {
@Override
public DateRange get() {
Date now = new Date();
return new DateRangeParam(
truncate(now, Calendar.DATE),
addDays(truncate(now, Calendar.DATE), 1)
);
}
},
YESTERDAY {
@Override
public DateRange get() {
Date now = new Date();
return new DateRangeParam(
addDays(truncate(now, Calendar.DATE), -1),
truncate(now, Calendar.DATE)
);
}
},
// ...省略本月、上月的实现...
;
}

这样,枚举中就包含了时间区间的含义了,通过调用枚举的 get 方法,如 TODAY.get() ,就能获取时间区间 DateRange 了。

这就是枚举的高级用法,『高级』其实指的不是技术上的高级,无非是实现了接口而已,而是把枚举需要表达的含义(如 TODAY 要表达的含义是今天到明天)表达清楚、表达完整。

进阶

🤔以上代码还有什么问题么?例如如何进行单元测试?