文章目录
- 1、复习枚举
- 2、自定义属性
- 3、自定义属性枚举类和常量的对比
- 4、常用方法
- 5、枚举自定义属性在开发中的应用:字典表
- 6、补充:入参校验
刚接触枚举时的例子太简单,就一个Season枚举类,里面四个常量值,后来开发中看到枚举中定义属性就很看不惯。这里梳理下Java枚举中定义属性,以及枚举在开发中的实际应用举例。
1、复习枚举
- Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一年的 12 个月份
- Java枚举类使用enum定义,各个常量之间用逗号分隔
enum Color
{
RED, GREEN, BLUE;
}
- 枚举类中引用每一个常量值,用类名.常量
public class Test
{
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
//输出RED
- 初学时的经典例子,和switch连用
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;
switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
//蓝色
- 内部类中使用枚举
public Class MyDemo{
enum Color
{
RED, GREEN, BLUE;
}
public void doSome(){
....
}
}
2、自定义属性
枚举类的语法结构虽然和普通类不一样,但是经过编译器之后产生的也是一个class文件。该class文件再反编译回来可以看到实际上是生成了一个类。该类继承了java.lang.Enum<E>
,且所有的枚举值都是 public static final 的,以上枚举类Color可理解为:
//java为单继承,因此不能再继承其他类
class Color extends java.lang.Enum
{
public static final Color RED = new Color();
//枚举中的常量,可以看作是一个个对象,看到这儿应该get到枚举中定义属性的操作了
public static final Color BLUE = new Color();
public static final Color GREEN = new Color();
}
看到上面的反编译代码,对枚举中定义成员变量,枚举值有属性值的操作应该捋顺了!举个定义属性的简单例子:
public enum Domain {
XB("11","西北"),
HD("13","华东"),
DB("14","东北"),
HB("15","华北");
private String code;
private String name;
Domain(String code,String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName(){
return name;
}
}
3、自定义属性枚举类和常量的对比
上面提到所有的枚举值都是 public static final 的,可能会有疑问:那直接用常量不得了?
===>
自定义属性枚举是常量的升级版,二者有各自的应用场景。当就只需要一个静态变量名 = 值
时,直接用常量自然最快,如:
但当你的一个值背后需要连着多个信息时,就只能用枚举+自定义属性来表达!
4、常用方法
enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
枚举类中常用的三个方法:values()、ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:
- values() 返回枚举类中所有的值
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样
- valueOf()方法返回指定字符串值的枚举常量
enum Color
{
RED, GREEN, BLUE;
}
public class Test
{
public static void main(String[] args)
{
// 调用 values()
Color[] arr = Color.values();
// 迭代枚举
for (Color col : arr)
{
// 查看索引
System.out.println(col + " at index " + col.ordinal());
}
// 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentException
System.out.println(Color.valueOf("RED"));
// System.out.println(Color.valueOf("WHITE"));
}
}
运行结果:
RED at index 0
GREEN at index 1
BLUE at index 2
RED
5、枚举自定义属性在开发中的应用:字典表
看一个例子,有一个审核流的需求,审核状态只有四种,定义字典表:
为了后面写代码方便和规范,定义下面这个枚举类
import brave.internal.Nullable;
import java.util.HashMap;
import java.util.Map;
public enum AuditStatusEnum {
BEFORE("before","待提交"),
WAIT("wait", "待审核"),
NO("no", "审核未通过"),
PASS("pass","审核通过");
String code;
String name;
AuditStatusEnum(String code, String name) {
this.code = code;
this.name = name;
}
private static final Map<String, AuditStatusEnum> mappings = new HashMap<>(5);
static {
for (AuditStatusEnum statusEnum : values()) {
mappings.put(statusEnum.code, statusEnum);
}
}
public String getCode(){
return code;
}
public String getName(){
return name;
}
@Nullable
public static AuditStatusEnum resolve(@Nullable String code) {
return (code != null ? mappings.get(code) : null);
}
}
分析:
- 两个属性,一个为code,一个为中文名,常见操作
- values()方法获取枚举成员的所有值,返回一个数组
- 写静态代码块,遍历上面的数组,以枚举值的code为键,以枚举值本身为值,放进Map集合
- 静态方法resolve()则是根据枚举值的code查询枚举值
- 定义静态方法resolve,妙在利用了Java类加载的时机之一 : 访问静态方法 。
访问静态方法 ==> 类加载 ==> 静态代码块执行 ⇒ 枚举值被全存到Map中
,方便后面查询
/定义了上面的字典对应的枚举类后,写业务逻辑代码:
if (xxDto.getAuditCode().equals(AuditStatusEnum.NO.getCode())){
return new MyException("审核未通过,不可操作!");
}
完
6、补充:入参校验
最后,工作时看到同事写的这个入参校验也蛮有意思,记录一下:
有个需求,它的某个接口有个传参,如排序字段orderField,该字段的可取值固定,只有create_time和name,为了防止非法传参,需要对这个入参做校验,此时也可用自定义属性的枚举类实现。
//当然想实现这个校验,直接把字段塞进集合,判断传参的值在不在集合中也能实现
//但这样一来后期扩展不方便,二来则是每个公司的代码规范要求
接下来演示枚举类:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
@Getter
@AllArgsConstructor
public enum OrderFieldEnum {
CREATE_TIME("createTime", "create_time"),
NAME("name","name");
private final String value; //值
private final String field; //对应数据库中的字段,方便后面写业务和Mapper层代码
private static final Map<String, OrderFieldEnum> map = new HashMap<>();
@JsonCreator
public static OrderFieldEnum check(String value) {
//这里的判断map为空则遍历枚举类的值放进Map中
//这个操作和上面的利用类加载时机初始化Map集合一个目的
if (map.isEmpty()) {
for (OrderFieldEnum orderFieldEnum : OrderFieldEnum.values()) {
map.put(orderFieldEnum.getValue(), orderFieldEnum);
}
}
//如果在Map中找不到对应的key和传入的字段相等,则认为非法传参,即不支持这个排序字段
if (!map.containsKey(value)) {
throw new MyExceptionHandler("不支持这个排序字段"); //自定义异常,在全局异常处理器处理
}
//否则返回整个枚举对象
return map.get(value);
}
@JsonValue
public String getValue() {
return value;
}
}
此时,校验入参可:
OrderFieldEnum.check(dto.getOrderField().getValue());
记录下他的另一种校验的实现方式,虽然写多了,但通用性更强
checkParamRegion(dto.getOrderField(), List.of(OrderFieldEnum.CREATE_TIME, OrderFieldEnum.NAME));
public void checkParamRegion(Object param, Collection<?> regions) {
//判断Object param是集合类型还是非集合
if (param instanceof List) {
List list = (List) param;
for (Object o : list) {
if (!regions.contains(o)) {
throw new MyExceptionHandler("参数超过了给定范围");
}
}
} else {
if (!regions.contains(param)) {
throw new MyExceptionHandler("参数超过了给定范围");
}
}
}
最后,写mapper层的排序,拿排序字段可:
OrderFieldEnum.CREATE_TIME.getField()