泛型和反射
约 1164 字大约 4 分钟
2025-07-18
Java 的部分反射 API 也使用了泛型,这为类型安全提供了更好的保障。
7.1 Class<T>
泛型
Class<T>
是一个泛型类,它代表一个类的类型。使用 Class<T>
可以避免在创建实例时进行强制类型转换。
Class clazz = String.class;
String str = (String) clazz.newInstance(); // compile warning
Class<String> clazz = String.class;
String str = clazz.newInstance(); // no warning
上述代码中,第一个例子因为 Class
没有指定泛型类型,所以 newInstance()
方法返回的是 Object
类型,需要强制转换为 String
类型。而第二个例子中,Class<String>
指定了泛型类型为 String
,所以 newInstance()
方法直接返回 String
类型,避免了强制类型转换,更加安全。
getSuperclass()
方法返回的 Class
类型是 Class<? super T>
,表示 T 的父类类型。
Class<? super String> sup = String.class.getSuperclass();
7.2 Constructor<T>
泛型
Constructor<T>
也是一个泛型类,它代表一个类的构造方法。通过 Constructor<T>
可以创建指定类型的对象。
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
这段代码演示了如何通过 Constructor<Integer>
创建一个 Integer
类型的对象。getConstructor(int.class)
方法获取 Integer
类的接受 int
类型参数的构造方法,然后通过 newInstance(123)
创建一个新的 Integer
对象,值为 123。
7.3 泛型数组
可以声明带泛型的数组,但不能直接使用 new
操作符创建带泛型的数组。
Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!
这是因为 Java 泛型是基于类型擦除实现的。在运行时,泛型类型信息会被擦除,所以无法直接创建泛型数组。
必须通过强制类型转换才能创建带泛型的数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
由于 Java 的泛型擦除机制,数组在运行时并没有泛型类型的概念,因此使用泛型数组需要特别小心,避免出现类型转换异常。
下面的代码演示了不安全地使用带泛型的数组:
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);
// ClassCastException:
Pair<String> p = ps[1];
String s = p.getFirst();
上述代码中,arr
是一个原始类型的数组,而 ps
是一个泛型数组。由于 arr
和 ps
引用的是同一个数组,所以可以通过 arr
放入 Pair<Integer>
类型的元素。当尝试从 ps
中获取元素时,由于期望的是 Pair<String>
类型,但实际获取的是 Pair<Integer>
类型,因此会抛出 ClassCastException
异常。
为了安全地使用泛型数组,必须避免使用原始类型的引用操作数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
这样,由于没有原始数组的引用,所有的操作都通过泛型数组 ps
进行,从而保证了类型安全。
带泛型的数组实际上是编译器的类型擦除导致的:
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
System.out.println(ps.getClass() == Pair[].class); // true
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
因此,不能直接创建泛型数组 T[]
,因为擦除后代码变为 Object[]
:
// compile error:
public class Abc<T> {
T[] createArray() {
return new T[5];
}
}
必须借助 Class<T>
来创建泛型数组:
T[] createArray(Class<T> cls) {
return (T[]) Array.newInstance(cls, 5);
}
Array.newInstance(cls, 5)
方法可以创建一个指定类型和长度的数组。
还可以利用可变参数创建泛型数组 T[]
:
public class ArrayHelper {
@SafeVarargs
static <T> T[] asArray(T... objs) {
return objs;
}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
7.4 谨慎使用泛型可变参数
虽然使用可变参数可以方便地创建泛型数组,但是需要谨慎使用,以避免潜在的类型安全问题。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] arr = asArray("one", "two", "three");
System.out.println(Arrays.toString(arr));
// ClassCastException:
String[] firstTwo = pickTwo("one", "two", "three");
System.out.println(Arrays.toString(firstTwo));
}
static <K> K[] pickTwo(K k1, K k2, K k3) {
return asArray(k1, k2);
}
static <T> T[] asArray(T... objs) {
return objs;
}
}
上述代码中,直接调用 asArray(T...)
方法似乎没有问题,但是在 pickTwo()
方法中,由于类型擦除,编译器无法检测 K[]
的正确类型,因此返回了 Object[]
,导致 ClassCastException
异常。
编译器会对所有可变泛型参数发出警告,除非确认完全没有问题,才可以使用 @SafeVarargs
注解消除警告。
注
如果在方法内部创建了泛型数组,最好不要将它返回给外部使用。
这是因为返回泛型数组可能会导致类型安全问题,如上述例子所示。