枚举工具类-反射获取枚举类中变量的所有值

开发背景:

在开发中需要用到枚举类中变量的所有值。如下例季节枚举类中需要获取数组["春", "夏", "秋", "冬"]。

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

最开始,我使用传统的方式获取,代码如下:

public class EnumUtilsDemo {
    public static void main(String[] args) {
        List seaconList = new ArrayList<>();
        for (Season value : Season.values()) {
            String season = value.getSeason();
            seaconList.add(season);
        }
        System.out.println(seaconList);
    }
}

由于最近在学习到了反射相关的知识,所以想编写利用反射编写一个通用的工具类。一来强化学习,一来以后可以通用工具类。

我的实现思路是这样的:通过分析上述代码发现,获取所有值需要知道所在的枚举类和要获取的字段。这里我发现枚举类中的Field类型只有(以Season为例)如下:

Arrays.stream(Season.class.getDeclaredFields()).forEach(System.out::println);
输出:
public static final model.Season model.Season.SPRING
public static final model.Season model.Season.SUMMER
public static final model.Season model.Season.AUTUMN
public static final model.Season model.Season.WINTER
java.lang.String model.Season.season
private static final model.Season[] model.Season.$VALUES

所以我通过传入枚举类的class与要获取值的类型的class来获取。以Season为例,传入Season.class,与字段season的String.class来获取。

具体实现如下:


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

        Field field = getField(enumClass, paramClass);
        //因为枚举中字段的可能设置为private,要通过Field.get()方法获取Field的值需要开启忽略获取访问符限制
		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);
    }
}

注释:这里我单独说下上述的getBasicClass方法:

  • 通过Integer的源码public static final Class  TYPE = (Class) Class.getPrimitiveClass("int");可以得知TYPE属性存放了其基本类型。
  • 首先尝试通过class的getField("TYPE")获取TYPE属性。
  • 如果获取TYPE属性成功,则将通过Field中的get(Object obj)获取TYPE属性中的值。
  •  通过Field源码中get方法的注释可以得知,获取静态的Filed传入的Object为null即可,(因为static属于类不属于对象,所以无需指明具体的对象)

测试一下:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        //原始方式获取
        List seaconList = new ArrayList<>();
        for (Season value : Season.values()) {
            String season = value.getSeason();
            seaconList.add(season);
        }
        System.out.println(seaconList);
        //反射工具类获取
        List values = getValues(Season.class, String.class);
        System.out.println(values);
    }
输出:
原始方式获取:[春, 夏, 秋, 冬]
反射工具类获取:[春, 夏, 秋, 冬]

这里需要说明,需要获取属性的类型只能存在一个,否则会无法正常获取,如下列枚举类Season新增字段String name,再次运行将导致程序异常。这种情况下只能用原始方式获取了。

public enum Season {
    SPRING("春", "SPRING"),
    SUMMER("夏", "SUMMER"),
    AUTUMN("秋", "AUTUMN"),
    WINTER("冬", "WINTER");
    String season;
    String name;

    Season(String season, String name) {
        this.season = season;
        this.name = name;
    }
    public String getSeason() {
        return season;
    }

    public String getName() {
        return name;
    }
}

再次运行输出:

原始方式获取:[春, 夏, 秋, 冬]
[Ljava.lang.reflect.Field;@4554617c
Exception in thread "main" java.lang.IllegalArgumentException: class java.lang.String类型属性数量异常。
	at EnumUtils.getField(EnumUtils.java:89)
	at EnumUtils.getValues(EnumUtils.java:45)
	at EnumUtils.main(EnumUtils.java:27)

总结

当然使用反射工具类也有个弊端,就是需要抛出比较多的异常,但主要还是学习吧。通过上述工具类的开发,涉及到不少知识点,在开发过程中对这些知识又进一步的加深了。

有没讲明白的地方可以留言说明,也请大佬名对程序不足之处指出说明。

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