文章目录
- 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() 返回枚举常量,不存在的会报错 IllegalArgumentExceptionSystem.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;}@Nullablepublic 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<>();@JsonCreatorpublic 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);}@JsonValuepublic 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()