从编译过程看java的泛型

我们为什么需要泛型??

1. 在1.5之前是没有泛型的,而通常使用object来泛化我们所有的对象,这样做也可以让我们达到泛型的目的,但是在代码编写的过程中很容易出现类型转换的错误,这种错误在编译期间是不知道的,只有到运行期间才知道。
比如:

  List list=new List();
  list.add("aaaa");
  list.add(12);
  int a= (int) list.get(0); //代码编写不报错,运行出错
  String s=(String) list.get(1);//代码编写不报错,运行出错

在上面的代码中,编译器只会编译出 list.get(0);返回的是object对象,而传给int a引用变量,是不会报错的,因为任何类型的父类都是object类型。
但是真正运行的时候会报错,原因就是运行期间虚拟机会找到list.get(0);的真正类型是String类型,传递给int a
肯定会出错的。
这些都是码代码的时候最容易出现的错误,这时候泛型类型出现了,在下面的2中给大家解答。

2. java语言的泛型基本上完全是在编译器中实现的,有编译器执行类型检测和类型推断,然后生成普通的非泛型的字节码,就是虚拟机完全无感知泛型的存在。这种实现技术称为擦除(erasure)。编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除。

代码实例:

  List<String> list=new List<String>();
  list.add("aaaa");
  list.add(12); //报错
  int a=list.get(0);//报错
  String s=list.get(1);//报错

在上面代码中,并不需要强转型,而且在我们编写这些代码的时候是有提示报错的,为啥呢?这就是泛型在编译期间起的作用了。在编译期间,编译器会将泛型中初始化的类型记住,在该泛型对象set/get操作的时候,自动给泛型对象进行类型检查和强制转型操作,所以出错就会在编译期间出现,而不会等到运行期间才发现错误。而在编译器将泛型类编译完成之后,泛型类的类型参数都被全部擦除,类参数初始化的泛型类其实是共用的一个泛型类字节码,而并不会一种类参数就生成一种对应的泛型类字节码。故才有,我们所说的 泛型的类型只在编译期间有效,运行期间jvm是看不见泛型的具体类型的。也可以看出来java的泛型实现其实就是编译器自动给字节码生成对应的安全操作代码,虚拟机只负责执行而已,泛型完全是由编译器实现的。

3. java5才引入的泛型,所以扩展虚拟机指令集来支持泛型被认为是无法接受的,因为这回为java厂商升级其JVM造成难以逾越的障碍。因此采用了可以完全在编译器中实现的擦除方法。

4. 类型擦除和多态的冲突和解决
如下代码所示:按照上面所说的类型会被擦除的话,那么B类继承A类,B的get/set方法按照常理来说是和父类A的get/set方法不同的,因此不能叫做重写,但是实际上就是重写,为啥呢,还是因为编译器在编译期间自动给B类补充了一个就、桥方法,也就是java中的Bridge设计模式。

class A<T>{
    private T t;
    public T get(){
        return t;
    }
    public void set(T t){
        this.t=t;
    }
}

class B extends A<Number>{
    private Number n;

    @Override
    public Number get(){
        return t;
    }

    @Override
    public void set(Number t){
        this.t=t;
    }
}

实际上,如果我们讲这段代码的编译后的代码看一看就会发现,B类中,编译器会帮我们生成两个方法,
一个是:

    @Override
    public Object get(){
        return this.get();//调用返回Number的那个方法
    }

还有一个是:

    @Override
    public void set(Object t ){
        this.set((Number)t ); //调用参数是Number的那个方法
    }

所以才会导致重写成功,实际上并不算我们想想中的重写,只是编译器帮我们使用了桥手段来完成重写。
还有一个疑问就是:我们在编写代码的时候其实是不能仅仅根据返回类型来判断函数是不同的(会报错:方法已经存在,重复定义),但是虚拟机可以通过参数类型和返回类型共同确定一个方法,所以编译器为了实现泛型的多态允许自己来做这样“看似不合法”的事情(典型的走后门),然后交给虚拟机自己去区别处理了。

猜你喜欢

转载自blog.csdn.net/u012345683/article/details/74858471