枚举工具类-通过给定值获取对应的枚举类

开发背景:

在开发过程中遇到需要通过给定的值来获取对应的枚举值,如下例枚举类中,需要通过传入"春"来获取SPRING。

public enum Season {
    SPRING("春"),
    SUMMER("夏"),
    AUTUMN("秋"),
    WINTER("冬");
    String season;
    Season(String season) {
        this.season = season;
    }
}

开始尝试通过Enum的内置方法valueOf()获取,使用报错:

public static void main(String[] args) {
    System.out.println(Season.valueOf("春"));
}

输出:
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant model.Season.春
	at java.lang.Enum.valueOf(Enum.java:238)
	at model.Season.valueOf(Season.java:10)
	at Demo.main(Demo.java:12)

我们打开valueOf源码看看他是通过什么值获取,

方法注释:
/**
     * Returns the enum constant of the specified enum type with the
     * specified name.  The name must match exactly an identifier used
     * to declare an enum constant in this type.  (Extraneous whitespace
     * characters are not permitted.)
**/
翻译为 
返回具有指定名称的指定枚举类型的枚举常量。 名称必须与用于声明此类型的枚举常量的标识符完全匹配。 (不允许使用多余的空格字符。)

需要通过与声明枚举类型的标识完全匹配,就是说我要通过"SPRING"字符串来获取SPRING枚举类,但我需要的是通过“春”来获取到SPRING。
后来我想到一种实现方法,通过获取枚举类的构造方法,然后反射执行其构造方法来获取。先显示的通过反射看看Season的构造方法长什么样子。

    public static void main(String[] args) {
        Constructor[] constructors = Season.class.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
    }
    
    输出:
    private model.Season(java.lang.String,int,java.lang.String)

???,小朋友头上多了好多问号,明明我的构造函数只有java.lang.String类型,为什么变成了三个参数了。

于是,我在Season中新增了一个无参构造,再次运行上述代码。


输出:

private model.Season(java.lang.String,int,java.lang.String)
private model.Season(java.lang.String,int)

发现无参构造默认加了两个参数,我们打开抽象类Enum看看,发现里面存在下列构造方法,且通过注释可以得知,程序员无需调用此构造方法,当声明枚举类时编译器会自动实现该方法,而参数name、ordinal分别是“与声明枚举类型标识完全匹配的字符串”和“枚举声明在枚举类中的序号,从0开始”。

/**
     * Sole constructor.  Programmers cannot invoke this constructor.
     * It is for use by code emitted by the compiler in response to
     * enum type declarations.
     *
     * @param name - The name of this enum constant, which is the identifier
     *               used to declare it.
     * @param ordinal - The ordinal of this enumeration constant (its position
     *         in the enum declaration, where the initial constant is assigned
     *         an ordinal of zero).
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

所以我需要通过传入name、ordinal、和自定义的值("春")来获取对应的SPRING。但是我可能不知道其name和ordinal。或者当我知道其name的情况下,我直接使用valueOf(name)直接就可以获取相关的枚举值了。所有此类方法行不通,而且Class类中的newInstance方法有这么一段代码,就是说如果通过newInstance来反射获取枚举类型的对象将抛出IllegalArgumentException异常。我们用代码试验一下: 

newInstance源码:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
	Constructor constructor = Season.class.getDeclaredConstructor(String.class, int.class, String.class);
	constructor.setAccessible(true);
	Season season = constructor.newInstance("SPRING", 0, "春");
	System.out.println(season);
}

输出:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at Demo.main(Demo.java:23)

奇怪的知识又多了。这个时候我想到通过利用我在上篇文章中的方法获取。(枚举工具类-反射获取枚举类中变量的所有值)

非反射代码实现如下,可以更好的理解设计思路:(需要在枚举类Season类中添加getSeason()方法)

public static void main(String[] args) {
        System.out.println(valueOf("春"));
    }
    public static Season valueOf(String season) {
        Season[] values = Season.values();
        for (Season value : values) {
            String seasonName = value.getSeason();
            if (seasonName.equals("春")) {
                return value;
            }
        }
        return null;
    }
}

对应的反射实现的工具类如下:

public class Demo {
    public static void main(String[] args) {
        System.out.println("正常方式获取:" + valueOf("春"));
        System.out.println("反射工具类获取:" + EnumUtils.valueOf(Season.class, "春"));
    }
    public static Season valueOf(String season) {
        Season[] values = Season.values();
        for (Season value : values) {
            String seasonName = value.getSeason();
            if (seasonName.equals("春")) {
                return value;
            }
        }
        return null;
    }
}

class EnumUtils {
/**
     * 获取枚举类中变量的列表
     * @param enumClass 枚举类的class
     * @param paramClass 参数类型的class
     * @param 
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static , T> List getValues(Class enumClass, Class paramClass)
            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        E[] values = values(enumClass);

        Field field = getField(enumClass, paramClass);
        field.setAccessible(true);
        List resultList = new ArrayList<>();
        for (E e : values) {
            T t = (T)field.get(e);
            resultList.add(t);
        }
        return resultList;
    }

    /**
     * 通过反射调用枚举类的values获取所有枚举类
     * @param enumClass
     * @param 
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static > E[] values(Class enumClass)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method valuesMethod = enumClass.getMethod("values");
        Object valuesObj = valuesMethod.invoke(enumClass);
        E[] values = (E[]) valuesObj;
        return values;
    }

    /**
     * 通过field的类型获取field
     * 说明:
     *      因为枚举类中只有Enum类型的Field,所有可以通过指定Field的类型来获取Field
     * @param enumClass
     * @param paramClass
     * @param 
     * @param 
     * @return
     */
    private static  Field getField(Class enumClass, Class paramClass) throws IllegalAccessException {
        //如果是包装类获取包装类的基本类型
        Class basicClass = getBasicClass(paramClass);
        //获取类型相同的Field(类型相同或与其基本类型相同)
        List fieldList = Arrays.stream(enumClass.getDeclaredFields())
                .filter(f -> f.getType() == paramClass || f.getType() == basicClass).collect(Collectors.toList());
        if (fieldList.size() != 1) {
            //抛出异常,只支持一个属性
            throw new IllegalArgumentException(paramClass + "类型属性数量异常。");
        }
        return fieldList.get(0);
    }

    /**
     * 获取class的基本类型的class
     * 说明:
     *      存在基本类型将返回其基本类型,否则返回null
     *      如传入Integer.class将返回int.class,传入String.class返回null
     * @param paramClass
     * @return
     * @throws IllegalAccessException
     */
    private static Class getBasicClass(Class paramClass) throws IllegalAccessException {
        Field typeField = null;
        try {
            //尝试获取包装类的TYPE
            typeField = paramClass.getField("TYPE");
        } catch (NoSuchFieldException e) {
            return null;
        }
        //获取包装类TYPE成功,获取TYPE属性值(因为类型为static,所以传入null)
        return (Class) typeField.get(null);
    }

    public static > E valueOf(Class eClass, Object... parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class[] paramClassArr = new Class[parameterTypes.length + 2];
        String name = (String) eClass.getMethod("name").invoke(null);
        int ordinal = (int) eClass.getMethod("ordinal").invoke(null);
        paramClassArr[0] = name.getClass();
        paramClassArr[1] = int.class;
        for (int i = 2; i < parameterTypes.length; i++) {
            paramClassArr[i] = parameterTypes[i].getClass();
        }
        Constructor constructor = eClass.getDeclaredConstructor(paramClassArr);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(name, ordinal, parameterTypes);
        return (E)o;
    }

    /**
     * 通过指定参数值获取枚举类型
     * @param enumClass
     * @param value
     * @return
     */
    public static > E valueOf(Class enumClass, Object value) {
        E[] values = null;
        Field field = null;
        try {
            field = getField(enumClass, value.getClass());
            field.setAccessible(true);
            values = values(enumClass);
            for (E e : values) {
                Object t = field.get(e);
                if (Objects.equals(t, value)) {
                    return e;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
输出:
正常方式获取:SPRING
反射工具类获取:SPRING

注:上述好多基本方法在文章 枚举工具类-反射获取枚举类中变量的所有值 中已详细说明,此处不冗余讲述,如有不清楚的地方可留言。也请大佬指出不足之处。

总结:

通过上述工具类的开发,触及到了好多知识盲区,不过正是这样也拓展了知识,同时加深了对知识点的理解。

当然在使用反射工具类时也有一些弊端,如需要=抛出比较多的异常。还有,上述工具类只能针对一个自定义的值,当存在多个自定义值时将不能实现。

这里我想到了一种实现方式,能够解决这种多值问题,但只针对多个自定义值的类型不一样的情况,如一个值类型时String,一个是int,这样可以通过类型获取到指定的值。但如果存在两个自定义的值都是String类型时,将无法通过反射知道两个String将如何分配,这里我没有想到好的方式解决。

由于时间关系,类型不同的多值实现将在下次更新。

你可能感兴趣的:(反射,java,enum,java,反射,enum)