18.6 反射和泛型

18.6 反射和泛型

JDK5以后,JavaClass类增加了泛型功能,从而允许使用泛型来限制Class类,例如, String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Classs<?>通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换

18.6.1 泛型和Class类

使用Class<T>泛型可以避免强制类型转换。

程序 简单对象工厂

例如,下面提供一个简单的对象工厂,该对象工厂可以根据指定类来提供该类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CrazyitObjectFactory {
public static Object getInstance(String clsName) {
try {
// 创建指定类对应的Class对象
Class cls = Class.forName(clsName);
// 返回使用该Class对象所创建的实例
return cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

问题 需要强制类型转换

上面程序中try块中的代码代码根据指定的字符串类型创建了一个新对象,但这个对象的类型是Object,因此当需要使用CrazyitObjectFactorygetInstance()方法来创建对象时,将会看到如下代码

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

甚至出现如下代码:

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

上面代码在编译时不会有任何问题,但运行时将抛出ClassCastException异常,因为程序试图将个Date对象转换成JFrame对象。
如果将上面的CrazyitobjectFactory工厂类改写成使用泛型后的Class,就可以避免这种情况。

程序 使用泛型后的对象工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import java.util.*;
import javax.swing.*;

public class CrazyitObjectFactory2 {
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对象。接下来当使用CrazyitObjectFactory2工厂类的getInstance()方法来产生对象时,无须使用强制类型转换,系统会执行更严格的检查,不会出现ClassCastException运行时异常.

Array类的创建数组方法(newInstance方法) 存在强制类型转换问题

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

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

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

1
static Object newInstance(Class<?> componentType, int length)

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

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

这样就可以在调用该方法后无须强制类型转换了

程序 包装Array类的newInstance方法解决强制类型转换问题

为了示范泛型的优势,可以对ArraynewInstance()方法进行包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.*;

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] = "HelloWorld";
// intArr是二维数组,初始化该数组的第二个数组元素
// 二维数组的元素必须是一维数组
intArr[1] = new int[] { 23, 12 };
System.out.println(arr[5]);
System.out.println(intArr[1][1]);
}
}

上面程序中粗体字代码定义的newInstance()方法对Array类提供的newInstance()方法进行了包装,将方法签名改成了
public static <T> T[] newInstance(Class<T> componentType, int length),
这就保证程序通过该newInstance()方法创建数组时的返回值就是数组对象,而不是Object对象,从而避免了强制类型转换。
程序在①行代码处将会有一个unchecked编译警告,所以程序使用了@SuppressWarnings来抑制这个警告信息。

18.6.2 使用反射来获取泛型信息

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

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

但这种方式只对普通类型的成员变量有效。如果该成员变量的类型是有泛型类型的类型,如Map<String,Integer>类型,则不能准确地得到该成员变量的泛型参数。
为了获得指定成员变量的泛型类型,应先使用如下方法来获取该成员变量的泛型类型

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

然后将Type对象强制类型转换为ParameterizedType对象,ParameterizedType代表被参数化的类型,也就是增加了泛型限制的类型

ParameterizedType类方法

方法 描述
Type[] getActualTypeArguments() 返回泛型参数的类型
Type getRawType() 返回没有泛型信息的原始类型。
Type getOwnerType() Returns a Type object representing the type that this type is a member of.

程序 获取成员变量的类型的泛型信息

下面是一个获取泛型类型的完整程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.*;
import java.lang.reflect.*;

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("获取泛型类型出错!");
}
}
}

运行上面程序,将看到如下运行结果:

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

从上面的运行结果可以看出:

  • 使用getType()方法只能获取普通类型的成员变量的数据类型;
  • 对于增加了泛型的成员变量,应该使用getGenericType()方法来取得其类型。

Type接口

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