首页 > 编程笔记 > Java笔记

Java通过反射操作泛型

学习完《Java泛型详解》一节中我们了解到,从Java 1.5 版本开始,Java 的 Class 类增加了泛型功能,从而允许使用泛型来限制 Class 类。例如,String.class 的类型实际上是 Class<String>。如果 Class 对应的类暂时未知,则使用 Class<?>。通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。

泛型和 Class 类

使用 Class<T> 泛型可以避免强制类型转换。例如,下面提供一个简单的对象工厂,该对象工厂可以根据指定类来提供该类的实例。
public class ObjectFactory {
    public static Object getInstance(String clsName) {
        try {
            // 创建指定类对应的Class对象
            Class cls = Class.forName(clsName);
            // 返回使用该Class对象创建的实例
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
上面程序中第 5 、7 行代码根据指定的字符串类型创建了一个新对象,但这个对象的类型是 Object,因此当需要使用 ObjectFactory 的 getInstance() 方法来创建对象时,代码如下:

// 获取实例后需要强制类型转换
Date d = (Date)ObjectFactory.getInstance("java.util.Date");

JFrame f = (JFrame)ObjectFactory .getInstance("java.util.Date");

上面代码在编译时不会有任何问题,但运行时将抛出 ClassCastException(强制类型转换异常),因为程序试图将一个 Date 对象转换成 JFrame 对象。

如果将上面的 ObjectFactory 工厂类改写成使用泛型后的 Class,就可以避免这种情况。
public class ObjectFactory2 {
    public static <T> T getInstance(Class<T> cls) {
        try {
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        // 获取实例后无须类型转换
        Date d = CrazyitObjectFactory2.getInstance(Date.class);
        JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class);
    }
}
在上面程序的 getInstance() 方法中传入一个 Class<T> 参数,这是一个泛型化的 Class 对象,调用该 Class 对象的 newInstance() 方法将返回一个 T 对象,如程序中第 4 行代码所示。接下来当使用 ObjectFactory2 工厂类的 getInstance() 方法来产生对象时,无须使用强制类型转换,系统会执行更严格的检查,不会出现 ClassCastException 运行时异常。

前面介绍使用 Array 类来创建数组时,曾经看到如下代码:

// 使用Array的newInstance方法来创建一个数组
Object arr = Array.newInstance(String.class, 10);

对于上面的代码其实使用并不是非常方便,因为 newInstance() 方法返回的确实是一个 String[] 数组,而不是简单的 Object 对象。如果需要将 arr 对象当成 String[] 数组使用,则必须使用强制类型转换,但是这是不安全的操作。

奇怪的是,Array 的 newInstance() 方法签名(方法签名由方法名称和参数列表组成)为如下形式:

public static Object newInstance(Class<?> componentType, int... dimensions)

在这个方法签名中使用了 Class<?> 泛型,但并没有真正利用这个泛型。如果将该方法签名改为如下形式:

public static <T> T[] newInstance(Class<T> componentType, int length)

这样就可以在调用该方法后无需强制类型转换了。不过,这个方法暂时只能创建一维数组,也就是不能利用可变个数的参数优势了。

为了示范泛型的优势,可以对 Array 的 newInstance() 方法进行包装。
public class CrazyitArray {
    // 对Array的newInstance方法进行包装
    @SuppressWarnings("unchecked")
    public static <T> T[] newInstance(Class<T> componentType, int length) {
        return (T[]) Array.newInstance(componentType, length); 
    }

    public static void main(String[] args) {
        // 使用 CrazyitArray 的 newInstance()创建一维数组
        String[] arr = CrazyitArray.newInstance(String.class, 10);
        // 使用 CrazyitArray 的 newInstance()创建二维数组
        // 在这种情况下,只要设置数组元素的类型是int[]即可
        int[][] intArr = CrazyitArray.newInstance(int[].class, 5);
        arr[5] = "新宝库Java教程";
        // intArr是二维数组,初始化该数组的第二个数组元素
        // 二维数组的元素必须是一维数组
        intArr[1] = new int[]{ 23, 12 };
        System.out.println(arr[5]);
        System.out.println(intArr[1][1]);
    }
}
上面程序中第 4、5、6、10 和 13 定义的 newInstance() 方法对 Array 类提供的 newInstance() 方法进行了包装,将方法签名改成了 public static <T> T[] newInstance(Class<T> componentType, int length),这就保证程序通过该 newInstance() 方法创建数组时的返回值就是数组对象,而不是 Object 对象,从而避免了强制类型转换。

提示:@SuppressWarnings("unchecked") 告诉编译器忽略 unchecked 警告信息,如使用 List,ArrayList 等未进行参数化产生的警告信息。程序在第 5 行代码处将会有一个 unchecked 编译警告,所以程序使用了 @SuppressWarnings 来抑制这个警告信息。

使用反射来获取泛型信息

通过指定类对应的 Class 对象,可以获得该类里包含的所有成员变量,不管该成员变量是使用 private 修饰,还是使用 public 修饰。获得了成员变量对应的 Field 对象后,就可以很容易地获得该成员变量的数据类型,即使用如下代码即可获得指定成员变量的类型。

// 获取成员变量 f 的类型
Class<?> a = f.getType();

但这种方式只对普通类型的成员变量有效。如果该成员变量的类型是有泛型类型的类型,如 Map<String, Integer>类型,则不能准确地得到该成员变量的泛型参数。

为了获得指定成员变量的泛型类型,应先使用如下方法来获取该成员变量的泛型类型。

// 获得成员变量f的泛型类型
Type gType = f.getGenericType();

然后将 Type 对象强制类型转换为 ParameterizedType 对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了如下两个方法。

下面是一个获取泛型类型的完整程序。
public class GenericTest {
    private Map<String, Integer> score;

    public static void main(String[] args) throws Exception {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用getType()取出类型只对普通类型的成员变量有效
        Class<?> a = f.getType();
        // 下面将看到仅输出java.util.Map
        System.out.println("score 的类型是:" + a);
        // 获得成员变量f的泛型类型
        Type gType = f.getGenericType();
        // 如果 gType 类型是 ParameterizedType对象
        if (gType instanceof ParameterizedType) {
            // 强制类型转换
            ParameterizedType pType = (ParameterizedType) gType;
            // 获取原始类型
            Type rType = pType.getRawType();
            System.out.println("原始类型是:" + rType);
            // 取得泛型类型的泛型参数
            Type[] tArgs = pType.getActualTypeArguments();
            System.out.println("泛型信息是:");
            for (int i = 0; i < tArgs.length; i++) {
                System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
            }
        } else {
            System.out.println("获取泛型类型出错!");
        }
    }
}
上面程序中的第 12、16、18 和 21 行代码就是取得泛型类型的关键代码。运行上面程序,将看到如下运行结果:

score 的类型是:interface java.util.Map
原始类型是:interface java.util.Map
泛型信息是:
第0个泛型类型是:class java.lang.String
第1个泛型类型是:class java.lang.Integer

从上面的运行结果可以看出,使用 getType() 方法只能获取普通类型的成员变量的数据类型。对于增加了泛型的成员变量,应该使用 getGenericType() 方法来取得其类型。

提示:Type 也是 java.lang.reflect 包下的一个接口,该接口代表所有类型的公共高级接口,Class 是 Type 接口的实现类。Type 包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

所有教程

优秀文章