【Java】泛型 之 泛型和反射

Java的部分反射API也是泛型。例如:Class<T>就是泛型:

java 复制代码
// compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();

// no warning:
Class<String> clazz = String.class;
String str = clazz.newInstance();

调用ClassgetSuperclass()方法返回的Class类型是Class<? super T>:

Class<? super String> sup = String.class.getSuperclass();

构造方法Constructor<T>也是泛型:

java 复制代码
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);

我们可以声明带泛型的数组,但不能用new操作符创建带泛型的数组:

java 复制代码
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];

使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps,因为它的类型是泛型数组。但是,编译器不会检查变量arr,因为它不是泛型数组。因为这两个变量实际上指向同一个数组,所以,操作arr可能导致从ps获取元素时报错,例如,以下代码演示了不安全地使用带泛型的数组:

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的引用:

java 复制代码
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];

上面的代码中,由于拿不到原始数组的引用,就只能对泛型数组ps进行操作,这种操作就是安全的。

带泛型的数组实际上是编译器的类型擦除:

java 复制代码
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[]:

java 复制代码
// compile error:
public class Abc<T> {
    T[] createArray() {
        return new T[5];
    }
}

必须借助Class来创建泛型数组:

java 复制代码
T[] createArray(Class<T> cls) {
    return (T[]) Array.newInstance(cls, 5);
}

我们还可以利用可变参数创建泛型数组T[]:

java 复制代码
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);

谨慎使用泛型可变参数

在上面的例子中,我们看到,通过:

复制代码
static <T> T[] asArray(T... objs) {
    return objs;
}

似乎可以安全地创建一个泛型数组。但实际上,这种方法非常危险。以下代码来自《Effective Java》的示例:

java 复制代码
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...)似乎没有问题,但是在另一个方法中,我们返回一个泛型数组就会产生ClassCastException,原因还是因为擦拭法,在pickTwo()方法内部,编译器无法检测K[]的正确类型,因此返回了Object[]

如果仔细观察,可以发现编译器对所有可变泛型参数都会发出警告,除非确认完全没有问题,才可以用@SafeVarargs消除警告。

如果在方法内部创建了泛型数组,最好不要将它返回给外部使用。

更详细的解释请参考《Effective Java》"Item 32: Combine generics and varargs judiciously"。

小结

  • 部分反射API是泛型,例如:Class,Constructor;
  • 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
  • 可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型;
  • 同时使用泛型和可变参数时需要特别小心。
相关推荐
csbysj20203 分钟前
C 标准库 - `<ctype.h>`
开发语言
郝学胜-神的一滴8 分钟前
计算机图形中的法线矩阵:深入理解与应用
开发语言·程序人生·线性代数·算法·机器学习·矩阵·个人开发
百锦再20 分钟前
第8章 模块系统
android·java·开发语言·python·ai·rust·go
没有bug.的程序员24 分钟前
Eureka 注册中心原理与服务注册发现机制
java·spring·云原生·eureka·架构·注册中心·服务注册发现
optimistic_chen25 分钟前
【Java EE进阶 --- SpringBoot】统一功能处理
java·spring boot·java-ee·json·统一功能处理
m0_5913389127 分钟前
day8鹏哥C语言--函数
c语言·开发语言·算法
oplp32 分钟前
回过头来重新对C语言进行深度学习(一)
c语言·开发语言
Ashlee_code33 分钟前
经纪柜台系统解析:从今日国际金融动荡看证券交易核心引擎的变革
python·架构·系统架构·区块链·vim·柜台·香港券商
西岭千秋雪_37 分钟前
Zookeeper数据结构
java·数据结构·分布式·zookeeper
青云交39 分钟前
Java 大视界 --Java 大数据机器学习模型在金融风险压力测试中的应用与验证
java·随机森林·机器学习·lstm·压力测试·联邦学习·金融风险