-
泛型的好处:
编译期强类型检查、无需进行显式类型转换。
-
通常情况下,T,E,K,V,? 是这样约定的:
? 表示不确定的 java 类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element
? 和 T 的区别
- T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
通配符可以使用超类限定而类型参数不行 - 类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
其他注意点:
不能new T
不能传<String> 给 <Object>,因为泛型不是协变的。
-
泛型擦除的原因:
Java 引入泛型擦除的原因是避免因为引入泛型而导致运行时创建不必要的类。那我们其实就可以通过定义类的方式,在类信息中保留泛型信息,从而在运行时获得这些泛型信息。所以,想 ArrayList<Integer> 和 ArrayList<String> 这两个实例,其类实例是同一个。
简而言之,Java 的泛型擦除是有范围的,即类定义中的泛型是不会被擦除的。
-
运行时通过反射获取泛型类型
signature 属性
Java泛型的擦除并不是对所有使用泛型的地方都会擦除的,部分地方会保留泛型信息。
Class文件实际保留了泛型信息,位于在Signature属性中,类型擦除只是对Code属性中的字节码
可以通过反射读取相关信息,其核心是Type类型,根据其不同的子类型,获取泛型信息,具体方法可以查看API文档
image
public class TypeTest<T>{
public List<T> list;
}
我通过反射可以获取到这list的声明类型 java.util.List<T>
Field list = TypeTest.class.getField("list");
Type genericType1 = list.getGenericType();
System.out.println("参数类型1:" + genericType1.getTypeName());
输出
参数类型1:java.util.List<T>
/大括号非常重要,相当于匿名内部类
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 的泛型擦除是有范围的,即类定义中的泛型是不会被擦除的。
List<Integer> list = new ArrayList<>();
list.getClass().getGenericSuperclass(); //获取不到泛型信息
List<Integer> list1 = new ArrayList() {};
list1.getClass().getGenericSuperclass(); //可以获取到泛型信息
可以看到第一个list由于是在运行时创建的对象所以由于泛型擦除是无法获取泛型信息的,因为运行时对象本质是方法的调用(真正调用了以后才会创建),在运行时创建的对象是没有办法通过反射获取其中的类型的。
第二个是可以获取的,因为后边加了{},这就使得这个list成为了一个匿名内部类且父类是List,子类是可以调用父类的构造方法的,加了之后这个list1就不是运行时创建的对象了而是编译时创建的,所以是可以获取泛型类型的。
我理解跟下面这个通过子类获取父类泛型类型是一样的:
class Child extends Parent<String>
//Child类中可通过如下代码获取到带<String>的Parent
((ParameterizedType)getClass().getGenericSuperclass().getActualTypeArguments()[0];
Java在编译的时候会检测父类的范型信息,因为子类声明了范型的类型并且在子类的代码中会使用到该类型,所以java会在生成的class中记录该子类声明的范型类型,所以只有在这种情况下运行时通过反射API可以取到该范型的类型。











网友评论