Java泛型很多人都用过,但是对于其中的原理可能很多人可能都不太清楚。
首先给出一个结论:Java的泛型是伪泛型,因为JVM在编译以后会将所有泛型参数擦除掉,这个就叫类型擦除。
下面用一段代码来证明,毕竟千言万语BB不如一句代码来的直接:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());
结果出人意料:竟然是True。
经过JVM编译以后,两个List的class都返回了原始类型List,并没有带上各自的泛型参数。
为什么会这样?!
这是因为泛型是Java 1.5以后才出现的,所以以前的java代码根本就没有尖括号泛型这玩意,
为了保持兼容性,Java在运行时简单粗暴的把泛型信息给擦除掉了!
不得不说,Java的这种做法相当差劲,简直就是垃圾设计!
但是以上代码的泛型信息真的全部都消失了吗。
让我们反编译字节码文件看一下:
javap -c -v TestTypeErace.class // java反汇编命令
输出了一堆东西,但是重点看一下 如下信息:
看见没有:虽然JVM擦除了对象的泛型参数,但是在编译阶段:泛型信息仍然保存到了java字节码文件中的LocalVariableTypeTable这张表里面。
所以Type接口这玩意儿就来了:Type接口可以通过反射获取到泛型类的泛型信息!
这就是Type接口出现的真正原因!
以下是Type接口的层级结构图
不得不说Java这门破语言的概念是相当多,然后起的名字又是狗屁不通,不好理解!
简单说一下:Type接口作为所有类型的总接口,包括了原始类型和泛型,反正就是生搬硬套的搞出了这么一个东西,为了保证兼容性,向低版本兼容。
下面简单解释一下这几个子接口的作用:
Class: 原生类型的Class对象,也就是我们日常所有的类/接口,包括枚举,数组,注解等,但不包括基本类型:如int, float等。
Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象
当一个类/接口被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象==》 这里实际涉及到classLoader的累加器机制和它的源码部分。
备注:Java所有的类和接口被JVM装载以后,都会生成一个跟类相关的java.lang.Class对象(字节码文件),每个类型都会有一个Class对象,Class类的对象只能由JVM创建,无法通过new来创建,当类/接口被加载时,就会被JVM自动生成Class对象,这也是反射的基础。
剩下的几种全部针对泛型:
这里就不用英文释义了,因为命名实在太差劲了,什么叫参数化类型,完全扯淡!垃圾命名!
TypeVariable: 简单说就是泛型尖括号里面的类型比如Map<T,V>, TypeVariable指的就是T和V
ParameterizedType: 声明带有泛型的类型: 也就是带有尖括号的类型,比如List<T>, Map<T,V>,
只要带尖括号就是ParameterizedType,也就是泛型类型
GenericArrayType: 简单说就是泛型数组: 首先他是一个数组,然后数组的变量是泛型
就这么简单,举几个例子:
List<String> , T[], Class<T>[] 这几个都是GenericArrayType
WildcardType: 通配符类型,这个不多说了,跟Class<?>差不多,实际用的很很少
这里重点讲讲ParameterizedType的两个重要Api:
getActualTypeArguments: 获取泛型参数列表:因为可能存在多个泛型,比如 SuperClass<T, V>,所以会返回 Type[] 数组
getRawType: 获取泛型尖括号前面的类型
总的来说Type的出现主要是为了配合泛型的反射操作,所以下面用一段代码简单证明一下:
public class BaseObj<T,U> {
private List<T> items;
private List<String> names;
private BaseObj baseObj1;
private BaseObj<T,U> baseObj2;
private List list;
private Map<T, U> map = new HashMap<>();
private T t;
private <E> T getItem(T t, E e) {
return t;
}
public BaseObj<T,U> test(List<T> items, BaseObj<Integer,Integer> nums, T t) {
return null;
}
public static void main(String[] args) {
System.out.println("---------------输出泛型类尖括号里的类型----------------------------------------");
// 用当前类名获取class对象无需getClass方法,只要类的实例才需要getClass方法
Class<BaseObj> baseObjClass = BaseObj.class;
TypeVariable<Class<BaseObj>>[] typeParameters = baseObjClass.getTypeParameters();
for (TypeVariable<Class<BaseObj>> typeParameter : typeParameters) {
System.out.println(typeParameter);
}
System.out.println("----------------输出泛型类属性中的泛型类型------------------------------------------------------");
Field[] declaredFields = BaseObj.class.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field.getType() + "=" + field.getName());
Type genericType = field.getGenericType(); // 获取完整泛型:类名<泛型>
if (genericType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) genericType;
System.out.println("Typename :" + pType.getTypeName()); // 获取完整泛型:全路径类名<泛型>
System.out.println("rawType :" + pType.getRawType()); // 获取泛型尖括号前面的类型
System.out.println("ownerType :" + pType.getOwnerType()); // 获取父类类型
// 获取泛型尖括号<>里面的实际数据类型,因为可能存在多个泛型,比如 SuperClass<T, V>,所以会返回 Type[] 数组
Type[] types = pType.getActualTypeArguments();
System.out.println("泛型参数列表: " + Arrays.asList(types));
System.out.println("------------------------------------------");
continue;
}
System.out.println("成员变量:"+field.getName() + "不是泛型变量!");
System.out.println("genericType =>" + genericType);
System.out.println("------------------------------------------");
}
}
可能你看的会有点懵逼,不要紧,你结合之前的文字说明,再结合代码注释就能慢慢看懂,因为我也是这么理解来的。