Java枚举中定义属性

刚接触枚举时的例子太简单,就一个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()

猜你喜欢

转载自blog.csdn.net/llg___/article/details/130997300