说到泛型,就不得说起“擦除”这个概念,相比于c#来说,java的泛型只存在于程序的源码中,在编译后的class文件中不存在,这个过程就是--泛型"擦除";所以,对于new ArrayList 和 new ArrayList 来说,两个对象在编译之后两者是一样的,通过反射均可以向集合中添加任意类型的对象;
编译前:
public static void main(String[] args) {
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
}
编译后:
public static void main(String[] args) {
new ArrayList();
new ArrayList();
}
可以看到,在编译后的class文件中,ArrayList所声明的泛型不存在了;由此可知,如果想在程序运行期间获取泛型,看似是一件不可能完成的事情!
Map map = new HashMap<>();
map.put(0, 0L);
map.put(1, 1L);
String a = JSON.toJSONString(map);
map = JSON.parseObject(a, new TypeReference>(){});
按照泛型擦除,泛型在编译后的class文件中就不存在了, 知会保留原始类型(Type的概念),那么TypeReference又是如何获取到具体的泛型类型的呢?
查看TypeReference的源码,发现在其构造方法中,主要使用到了getGenericSuperclass()和getActualTypeArguments()两个方法,getGenericSuperclass()返回的是此对象带“泛型”的父类,而getActualTypeArguments()返回的是此父类中实际类型参数的Type 对象数组,说白了就是TypeReference<>中的泛型;
如果非匿名内部类,通过getActualTypeArguments() 获取的仍然是Type中的概念;如果声明了匿名内部类,则在编译时会将此匿名内部类但对生成一个class文件
创建的匿名内部类new TypeReference>(){},在生成的的class文件中,编译器默认为TypeReference中的泛型Map原始类型,并不会进行擦除!类似于 我们实际创建一个类 Class TypeReference>{} ,和Class Test{}一样,在编译后Map、T依旧会存在!
由此,对于java泛型的“擦除”并不能一概而论,在运行期间,如果方法中出现带泛型的匿名内部类,那么泛型依旧会被保留下来,我们可以通过对应的方法获取到实际的泛型类型!