解决gson封装泛型类型擦除问题(一) - gmtalang/test GitHub Wiki
*参考
-
public class Result<DATA_TYPE> {
-
private int code;
-
private String message;
-
private DATA_TYPE data;
-
}
-
要明白什么是类型擦除?泛型只存在于代码的编译阶段,在进入jvm之前会被擦除
-
问题:当声明为泛型,对已经实例化的对象,它的类型是确定的不能更改够则编译器会报错
-
如下案例:
-
public class Cache {
-
T value;
-
public Object getValue() {
-
return value;
-
}
-
public void setValue(T value) {
-
this.value = value;
-
}
-
}
如下案例查找一下问题
-
class Base{}
-
class Sub extends Base{}
-
Sub sub = new Sub();
-
Base base = sub;
-
上面代码显示,Base 是 Sub 的父类,它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值,那么
-
List lsub = new ArrayList<>();
-
List lbase = lsub;
-
最后一行代码成立吗?编译会通过吗?
-
答案是否定的。 编译器不会让它通过的。Sub 是 Base 的子类,不代表 List 和 List 有继承关系。
-
但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。
-
所以,通配符的出现是为了指定泛型中的类型范围。
-
通配符有 3 种形式。
- > 被称作无限定的通配符。
- extends T> 被称作有上限的通配符。
- super T> 被称作有下限的通配符。
-
结论:类型擦除的时候,如果T没有指定的上限,那么擦除后的类型为基类Object;如果T指定的有上限,那么类型擦除后就是该上限的类型
-
通过反射我们可以解决如下问题:
-
正常情况下,因为泛型的限制,编译器不让最后一行代码编译通过,因为类似不匹配,但是,基于对类型擦除的了解,利用反射,我们可以绕过这个限制。
-
public interface List extends Collection{
-
boolean add(E e);
-
}
-
- 上面是 List 和其中的 add() 方法的源码定义。
-
- 因为 E 代表任意的类型,所以类型擦除时,add 方法其实等同于
-
- boolean add(Object obj);
-
那么,利用反射,我们绕过编译器去调用 add 方法。
-
public class ToolTest {
-
public static void main(String[] args) {
-
List<Integer> ls = new ArrayList<>();
-
ls.add(23);
-
// ls.add("text");
-
try {
-
Method method = ls.getClass().getDeclaredMethod("add",Object.class);
-
method.invoke(ls,"test");
-
method.invoke(ls,42.9f);
-
} catch (NoSuchMethodException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
} catch (SecurityException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
} catch (IllegalAccessException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
} catch (IllegalArgumentException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
} catch (InvocationTargetException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
for ( Object o: ls){
-
System.out.println(o);
-
}
-
}
-
}
-
打印结果是:
-
23
-
test
-
42.9
-
可以看到,利用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制。
-
Java 不能创建具体类型的泛型数组
-
这句话可能难以理解,代码说明。
-
List[] li2 = new ArrayList[];
-
List li3 = new ArrayList[];
-
两行代码是无法在编译器中编译通过的。原因还是类型擦除带来的影响。
-
List 和 List 在 jvm 中等同于 List ,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List 类型还是 List 类型。
-
但是,
-
List>[] li3 = new ArrayList>[10];
-
li3[1] = new ArrayList();
-
List<?> v = li3[1];
-
借助于无限定通配符却可以,前面讲过 ? 代表未知类型,所以它涉及的操作都基本上与类型无关,因此 jvm 不需要针对它对类型作判断,因此它能编译通过,但是,只提供了数组中的元素因为通配符原因,它只能读,不能写。比如,上面的 v 这个局部变量,它只能进行 get() 操作,不能进行 add() 操作