根據值獲取枚舉類對象工具類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();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章