问:Java 泛型在编译时被擦出后还有办法在运行时获取泛型信息吗?怎么获取?
答:有办法,正因为泛型在编译时擦出所以会有如下现象。
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); //true
ArrayList<Integer> 和 ArrayList<String> 在编译的时候是完全不同的类型,我们无法在写代码时把一个 String 类型的实例加到 ArrayList<Integer> 中,但是在程序运行时的确会输出 true,因为不管是 ArrayList<Integer> 还是 ArrayList<String> 在编译时都会被编译器擦除成了 ArrayList。
运行时被擦出的泛型信息获取其实主要依赖反射包下的 ParameterizedType 泛型参数类型接口,应用也比较广泛,譬如 Gson 框架就是这么做的,如下是一段获取代码示例:
//大括号非常重要,相当于匿名内部类
Map<String, Integer> map = new HashMap<String, Integer>() {};
Type type = map.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = ParameterizedType.class.cast(type);
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
System.out.println(typeArgument.getTypeName());
}
/* Output
java.lang.String
java.lang.Integer
*/
上面这段代码展示了如何获取 map 这个实例所对应类的泛型信息,其中最重要的就是第一行 map 实例的创建,是一个匿名内部类且为 HashMap 的子类,泛型参数限定为 String 和 Integer。
Java 引入泛型擦除的原因是避免因为引入泛型而导致运行时创建不必要的类,所以我们可以通过定义类的方式在类信息中保留泛型信息,从而在运行时获得这些泛型信息,所以 Java 的泛型擦除是有范围的,即类定义中的泛型是不会被擦除的。