Java核心技术1-泛型反射

  |  

摘要: 本文是《Java核心技术 10th》中关于泛型反射的要点总结。

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


使用泛型机制编写的程序代码要比那些杂乱地使用 Object 变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList 就是一个无处不在的集合类。

泛型很像 C++ 中的模板。与 Java 一样,在 C++ 中,模板也是最先被添加到语言中支持强类型集合的。后续发现模板还有其他的用武之地。Java 也一样,泛型在程序中有很多除了集合外的其它用途。

但注意,从表面上看,Java 的泛型类虽然类似于 C++ 的模板类,唯一明显的不同是 Java 没有专用的 template 关键字。但是,在本文中可以看到,这两种机制有着本质的区别。

本文是《Java核心技术1》第10版 Chap8 中关于泛型反射的要点总结。


泛型与反射

反射允许在运行时分析任意的对象。如果对象是泛型类的实例,由于类型擦除,关于泛型类型参数则得不到太多信息。

泛型 Class 类

Class 类是泛型的,例如 String.class 实际上是 Class<String> 类的对象。

下面是 Class<T> 中的一些方法,用了类型参数,使用类型参数可以使得 Class<T> 方法的返回类型更有针对性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
T newInstance() 
// 返回无参数构造器构造的新实例

T cast(Object obj)
// 如果 obj 为 null 或有可能转换成类型 T,则返回 obj;否则抛出 BadCastException 异常

T[] getEnumConstants()
// 如果 T 是枚举类型,则返回所有值组成的数组,否则返回 null

Class<? super T> getSuperclass()
// 返回这个类的超类,如果 T 不是一个类或 Object,则返回 null

Constructor<T> getConstructor(Class... parameterTypes)
Constructor<T> getDeclaredConstructor(Class... parameterTypes)
// 获得公有的构造器,或带有给定参数类型的构造器

使用 Class<T> 参数进行类型匹配

有时需要匹配泛型方法中的 Class<T> 参数的类型变量,例如:

1
2
3
public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException {
return new Pair<>(c.newInstance(), c.newInstance());
}

如果调用 makePair(Employee.class),Employee.class 是 Class<Employee> 的一个对象,makePair 方法的类型参数 T 与 Employee 匹配,并且编译器可以推断出这个方法返回 Pair<Employee>

虚拟机中的泛型类型信息

Java 泛型的关键特性之一是在虚拟机中泛型类型的擦除

但是擦除的类仍然保留一些泛型祖先的微弱记忆。一个 Pair 类型的对象无法区分是由 Pair<String> 构造的还是由 Pair<Employee> 构造的。但原始 Pair 类知道源于泛型类 Pair<T>

考虑下面的方法:

1
public static Comparable min(Comparable[] a)

这是一个泛型方法的擦除:

1
public static <T extends Comparable<? super T>> T min(T[] a)

可以用反射 API 来确定以下问题:

  • 这个泛型方法有一个叫做 T 的类型参数。
  • 这个类型参数有一个子类型限定,其自身又是一个泛型类型。
  • 这个限定类型有一个通配符参数。
  • 这个通配符参数有一个超类型限定。
  • 这个泛型方法有一个泛型数组参数。

为了表达泛型类型声明,使用 java.lang.reflect 包中提供的接口 Type。这个接口包含下列子类型:

  • Class 类,描述具体类型。
  • TypeVariable 接口,描述类型变量(如 T extendsComparable<? super T>)。
  • WildcardType 接口,描述通配符(如 ? super T)。
  • ParameterizedType 接口,描述泛型类或接口类型(如 Comparable<? super T>)。
  • GenericArrayType 接口,描述泛型数组(如 T[])。

Type 类的继承层次如下图

在文章 Java核心技术1-反射 中有一个用反射 API 打印给定类的内容的代码,用 Pair 类运行后,会打印一下信息:

1
2
3
4
5
class Pair<T> extends java.lang.Object
public T getFirst()
public T getSecond()
public void setFirst(T)
public void setSecond(T)

Java核心技术1-泛型 中的 ArrayAlg 类运行,得到以下信息:

1
2
class ArrayAlg extends java.lang.Object
public static <T extends java.lang.Comparable> Pair<T> minmax(T[])

用泛型反射API打印出给定类的内容(代码模板)

完整代码如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import java.lang.reflect.Method;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.WildcardType;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Scanner;

public class GenericReflectionTest {
public static void main(String[] args) {
String name;
if(args.length > 0) {
name = args[0];
} else {
try (Scanner in = new Scanner(System.in)) {
System.out.println("Enter class name (e.g. java.util.Collection): ");
name = in.next();
}
}

try {
// 打印类和公有方法的泛型信息
Class<?> cl = Class.forName(name);
printClass(cl);
for(Method m: cl.getDeclaredMethods()) {
printMethod(m);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

public static void printClass(Class<?> cl) {
System.out.print(cl);
printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
Type sc = cl.getGenericSuperclass();
if(sc != null) {
System.out.print(" extends ");
printType(sc, false);
}
printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
System.out.println();
}

public static void printMethod(Method m) {
String name = m.getName();
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" ");
printTypes(m.getTypeParameters(), "<", ", ", ">", true);

printType(m.getGenericReturnType(), false);
System.out.print(" ");
System.out.print(name);
System.out.print("(");
printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
System.out.println(")");
}

public static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition) {
if(pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })){
return;
}
if(types.length > 0) {
System.out.print(pre);
}
for(int i = 0; i < types.length; i++) {
if(i > 0) {
System.out.print(sep);
}
printType(types[i], isDefinition);
}
if(types.length > 0) {
System.out.print(suf);
}
}

public static void printType(Type type, boolean isDefinition) {
if(type instanceof Class) {
Class<?> t = (Class<?>) type;
System.out.print(t.getName());
} else if (type instanceof TypeVariable) {
TypeVariable<?> t = (TypeVariable<?>) type;
System.out.print(t.getName());
if(isDefinition) {
printTypes(t.getBounds(), " extends ", " & ", "", false);
}
} else if (type instanceof WildcardType) {
WildcardType t = (WildcardType) type;
System.out.print("?");
printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
printTypes(t.getLowerBounds(), " super ", " & ", "", false);

} else if (type instanceof ParameterizedType) {
ParameterizedType t = (ParameterizedType) type;
Type owner = t.getOwnerType();
if(owner != null) {
printType(owner, false);
System.out.print(".");
}
printType(t.getRawType(), false);
printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
} else if (type instanceof GenericArrayType) {
GenericArrayType t = (GenericArrayType) type;
System.out.print("");
printType(t.getGenericComponentType(), isDefinition);
System.out.print("[]");
}
}
}

总结

我们已经知道了如何使用泛型类以及在必要时如何自定义泛型类和泛型方法。以及如何解译在 API 文档和错误消息中遇到的泛型类型声明。


Share