解决gson封装泛型类型擦除问题(一) - gmtalang/test GitHub Wiki

*参考

  • http://blog.csdn.net/briblue/article/details/76736356

  • 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() 操作

⚠️ **GitHub.com Fallback** ⚠️