根据值获取枚举类对象工具类EnumUtils

一,背景:

项目中枚举用的比较多,使用枚举时经常要根据枚举某个属性获取枚举对象,这就导致了我们每个枚举类里都要定义一个获取的方法,例如

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author kismet
 * @version 1.0
 * @since 2019/8/30
 */
@Getter
@AllArgsConstructor
public enum ActivityTypeEnum {

    /**
     * 1,秒杀活动;service的key即为seckillActivityService
     */
    SECKILL(1, "秒杀活动", "seckillActivityService"),

    /**
     * 2,折扣活动;service的key即为discountActivityService
     */
    DISCOUNT(2, "折扣活动", "discountActivityService");

    private Integer key;

    private String desc;

    /**
     * 对应的service实现类key
     */
    private String service;

    /**
     * 根据对应的值获取枚举实例方法
     * 
     * @param key
     * @return
     */
    public static ActivityTypeEnum getEnumByType(Integer key) {
        for (ActivityTypeEnum item : ActivityTypeEnum.values()) {
            if (item.getKey().equals(key)) {
                return item;
            }
        }
        throw new RuntimeException("通过key获取枚举类异常");
    }
}

二,实现方式

每个枚举类里都要定义一个获取的方法这样相当的不优雅
我们通过注解加反射的方式来实现一个枚举操作的工具类来实现上面的方式

1,注解

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

/**
 * @author kismet
 * @version 1.0
 * @Description 枚举类对应的key值
 * @since 2019/9/9
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, METHOD})
public @interface EnumKey {
}

2,工具类EnumUtils

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

/**
 * @author kismet
 * @version 1.0
 * @Description 枚举类工具类
 * @since 2019/9/9
 */
public abstract class EnumUtils {

    /**
     * Title: valueOf Description:
     *
     * @param enumClass 枚举类
     * @param value     枚举实例key(贴有注解@EnumKey)
     * @author kismet
     * @date 2019/9/9 16:37
     */
    public static <E extends Enum<E>> E valueOf(Class<E> enumClass, Object value) {
    	 if (Objects.isNull(value)) {
            return null;
        }
        if (Objects.isNull(enumClass)) {
            throw new IllegalArgumentException("enumClass can't be null");
        }
        // 找到枚举类中贴有@EnumKey注解的字段
        Optional<Field> enumKeyField = findEnumKeyField(enumClass);
        if (!enumKeyField.isPresent()) {
            throw new IllegalArgumentException(
                    enumClass + " must have a field with the annotation " + EnumKey.class);
        }
        Field field = enumKeyField.get();
        E[] enumConstants = enumClass.getEnumConstants();
        for (E enumConstant : enumConstants) {
            try {
                if (value.equals(field.get(enumConstant))) {
                    return enumConstant;
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        throw new IllegalArgumentException(value + " can't convert to " + enumClass);
    }

    /**
     * 找到枚举类中贴有@EnumKey注解的字段
     *
     * @param enumClass
     * @param <E>
     * @return
     */
    private static <E extends Enum<E>> Optional<Field> findEnumKeyField(Class<E> enumClass) {
        return Arrays.stream(enumClass.getDeclaredFields())
                //过滤出有EnumKey注解的属性
                .filter(field -> Objects.nonNull(field.getAnnotation(EnumKey.class)))
                //将私有属性设为可以获取值
                .peek(field -> field.setAccessible(Boolean.TRUE)).findAny();
    }
}

3,测试枚举类

  • PayTypeEnum
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author kismet
 * @version 1.0
 * @since 2019/8/30
 */
@Getter
@AllArgsConstructor
public enum PayTypeEnum {

    /**
     * 1,现金;
     */
    CASH(1, "现金"),

    /**
     * 2,刷卡;
     */
    CARD(2, "刷卡");

    @EnumKey // 贴有注解EnumKey 即表示通过该字段来获取枚举
    private Integer code;

    private String desc;
}

  • ActivityTypeEnum
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author kismet
 * @version 1.0
 * @since 2019/8/30
 */
@Getter
@AllArgsConstructor
public enum ActivityTypeEnum {

    /**
     * 1,秒杀活动;service的key即为seckillActivityService
     */
    SECKILL(1, "秒杀活动", "seckillActivityService"),

    /**
     * 2,折扣活动;service的key即为discountActivityService
     */
    DISCOUNT(2, "折扣活动", "discountActivityService");

    @EnumKey  // 贴有注解EnumKey 即表示通过该字段来获取枚举
    private Integer key;

    private String desc;

    /**
     * 对应的service实现类key
     */
    private String service;

}

这样当我们需要通过某个值来获取该枚举时,主要再相应的字段上加上@EnumKey即可
不用再写获取枚举的方法,直接通过 EnumUtils.valueOf来得到

3,测试结果

import org.junit.Test;

/**
 * @author kismet
 * @version 1.0
 * @Description
 * @since 2019/9/9
 */
public class EnumTest {
    @Test
    public void build3() {
        ActivityTypeEnum activityTypeEnum = EnumUtils.valueOf(ActivityTypeEnum.class, 1);
        System.out.println(activityTypeEnum.name());
        PayTypeEnum payTypeEnum = EnumUtils.valueOf(PayTypeEnum.class, 1);
        System.out.println(payTypeEnum.name());
    }
}

结果打印:
SECKILL
CASH

三,优化

每次获取时都要通过反射去获取花费性能,
我们可以再第一次获取某个枚举时将其维护到一个 Map<Class<?>, Map<Object, Enum<?>>>
后面获取时就能直接从map中取提高性能

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;

/**
 * @author kismet
 * @version 1.0
 * @Description 枚举类工具类
 * @since 2019/9/9
 */
public abstract class EnumUtils {
    /**
     * 枚举类map;class为枚举的class;object为枚举实例的key(贴有注解@EnumKey);Enum<?>对应的枚举实例
     */
    private static final Map<Class<?>, Map<Object, Enum<?>>> ENUM_TYPE_TO_ENUM_VALUE_TO_ENUM_TABLE = new HashMap<>();

    /**
     * Title: valueOf Description:
     *
     * @param enumClass 枚举类
     * @param value     枚举实例key(贴有注解@EnumKey)
     * @author kismet
     * @date 2019/9/9 16:37
     */
    public static <E extends Enum<E>> E valueOf(Class<E> enumClass, Object value) {
        if (Objects.isNull(value)) {
            return null;
        }
        if (Objects.isNull(enumClass)) {
            throw new IllegalArgumentException("enumClass can't be null");
        }
        // 当ENUM_TABLE没有该enumClass类型时将其添加到ENUM_TABLE中
        if (!ENUM_TYPE_TO_ENUM_VALUE_TO_ENUM_TABLE.containsKey(enumClass)) {
            addEnumClass(enumClass);
        }
        //put only,check in put
        E e = (E) ENUM_TYPE_TO_ENUM_VALUE_TO_ENUM_TABLE.get(enumClass).get(value);
        if (Objects.isNull(e)) {
            throw new IllegalArgumentException(value + " can't convert to " + enumClass);
        }
        return e;
    }

    private synchronized static <E extends Enum<E>> void addEnumClass(Class<E> enumClass) {
        //double check
        if (!ENUM_TYPE_TO_ENUM_VALUE_TO_ENUM_TABLE.containsKey(enumClass)) {
            Optional<Field> getValueFieldOptional = findEnumKeyField(enumClass);
            if (!getValueFieldOptional.isPresent()) {
                throw new IllegalArgumentException(
                        enumClass + " must have a field with the annotation " + EnumKey.class);
            }
            getValueFieldOptional.ifPresent(field -> register(enumClass, enumConstant -> {
                try {
                    return field.get(enumConstant);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }));
        }
    }

    private static <E extends Enum<E>> void register(Class<E> enumClass, Function<E, Object> getValueFunction) {
        E[] enumConstants = enumClass.getEnumConstants();
        Map<Object, Enum<?>> valueToEnumMap = new HashMap<>();
        for (E enumConstant : enumConstants) {
            Object enumConstantValue = getValueFunction.apply(enumConstant);
            valueToEnumMap.put(enumConstantValue, enumConstant);
        }
        // 将enumClass对应的map变为不可变map;
        ENUM_TYPE_TO_ENUM_VALUE_TO_ENUM_TABLE.put(enumClass, Collections.unmodifiableMap(valueToEnumMap));
    }

    private static <E extends Enum<E>> Optional<Field> findEnumKeyField(Class<E> enumClass) {
        return Arrays.stream(enumClass.getDeclaredFields())
                //过滤出有EnumKey注解的属性
                .filter(field -> Objects.nonNull(field.getAnnotation(EnumKey.class)))
                //将私有属性设为可以获取值
                .peek(field -> field.setAccessible(Boolean.TRUE)).findAny();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章