Java 泛型擦除

53 阅读3分钟

1、啥是泛型擦除

泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉。

2、真相那个是啥,举例

  • 真相就是泛型在jvm都会被替换为Object
  • List list = new ArrayList< String>();左边编译时声明的静态类型,右边是运行时实现的实际类型
  • 那么list.add时则不会限制类型
public static void main(String[] args) {
    List list = new ArrayList();
    List listString = new ArrayList<String>();
    List listInteger = new ArrayList<Integer>();

    list.add(1);
    listString.add(123);
    listInteger.add("kentwang");

    System.out.println(list.size()+""+listString.size()+""+listInteger.size()+"");
    System.out.println(list.size()+""+listInteger.get(0));
}
  • 之所以get返回值时Object类型时因为在ArrayList 已经将泛型E擦除了,全都变为了Object,即使后边有类型转换,或者运行而不会报错。 image.png image.png image.png image.png

3、会有啥问题

3.1、强制类型转换会有编译器无法检测的问题。

List listInteger = new ArrayList<Integer>();
listInteger.add("KENT");
System.out.println(""+(int)listInteger.get(0));
  • 这个不会报错,我申明是list,实现是ArrayList< Integer>,编译时静态类型是List。add可以加任何类型。运行时是ArrayList< Integer>实际类型,我强转为(Integer)应该的嘛,但是会出错

3.2、引用传递问题

  • 上面的问题中,我们已经说过了T将在后期被转义成Object,那我们对引用也进行一个转化,是否行得通呢?
//都会报错
List<String> listObject = new ArrayList<Object>();
List<Object> listObject = new ArrayList<String>();

假设说我们的第一种方案是正确的,那么其实就是将一堆Object数据存入,然后再由上面所说的强制转化一般,转化成String类型,听起来完全ok,因为在List中本来存储数据的方式就是Object。但其实是会出现ClassCastException的问题,因为Object是万物的基类,但是强转是为子类向父类准备的措施。

再来假设说我们的第二种方案是正确的,这个时候,根据上方的数据String存入,但是有什么意义存在呢?最后都还是要成Object的,你还不如就直接是Object

4、解决方案

5、要用Java泛型擦除的原因

  • java泛型是伪泛型,泛型擦除就是代码在编译阶段,与泛型相关的信息会被清除。
  • 泛型本质上是给编译器检查用的,并不改变运行时的类型。
  • java5新增泛型,如果用真泛型
    • 修改 JVM 源代码,让 JVM 能正确的的读取和校验泛型信息
    • 为了兼容老程序,需为原本不支持泛型的 API 平行添加一套泛型 API(主要是容器类型)。
    • java5之下的库A,要改代码,假设B项目升级到Java5,那么,A项目必要时要升级Java5,并且改代码。
    • 因为在 Java 中不支持高版本的 Java 编译生成的 class 文件在低版本的 JRE 上运行,如果尝试这么做,就会得到 UnsupportedClassVersionError 错误。如下图所示:
ArrayList<User> users = new ArrayList<>(); ArrayList<Dog> dogs = new ArrayList<>(); System.out.println(users.getClass() == dogs.getClass()); // 输出 true

image.png

  • 思来想去怎么让老版本不该,新版本改呢?我不增加API,在编译器检查时限制你,然后在编译器里擦除变为Object,再jvm里面新老版本就一样了。