java翻译泛型方法原理小结

在java的虚拟机中并不存在实际的泛型类,而是将所有的泛型类编译成一个原始类型(raw type),也就是所谓的类型擦除,简单的说,从程序员的角度来看普通的泛型类应该是一族类,而在虚拟机看来,所有泛型类都是一个原始类(不支持实际意义上的泛型类),而几乎所有的泛型功能都通过编译器来映射成字节码,而与虚拟机无关

之所以虚拟机不支持实际意义上的泛型类,是由于早期的泛型类是由继承产生的,这样做也同样带来一个好处,即不会产生如c++那样的代码膨胀。


当翻译一个泛型表达式时实际上编译器将翻译两条虚拟机指令:

  1. 调用原始类的方法
  2. 将原始类强制转换成

例如:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

以上取自java核心技术,但是按照对之前文章的理解,在这两步之前应该还有类型检查,即当你使用<Employee>时会检查buddies是否是Employee类型,当然这一部也会由编译器翻译成字节码完成,而无需程序猿了解原理。


当泛型方法被类型擦除的适合会出现一些复杂的问题。首先一个是对于超类方法的调用:

请看如下的示例:

class DateInterval extend Pair<Date>
{
    public void setSecond(Date second)
    {
        if(second.compareTo(getFirst())>=0
        supper.setSecond(second);
    }
}

其中Pair是一个Date对象,需要覆盖这个方法的类第二个值永远不小于第一个值。当类型擦除的时候从虚拟机看来这个代码变成如下形式:

class DateInterval extend Pair 
{
    public void setSecond(Date second)  {   }
}

但是经过类型擦除的方法和原先的原始类型的方法有什么不同呢?

class DateInterval extend Pair 
{
    public void setSecond(Object second)    {   }
}

这显然是两个不同的方法,编辑器通过产生一个桥方法(bridge method)来调用我们希望调用的方法,类似如下:

public void setSecond(Object second)
{
    setSecond((Date) second);
}

即对于<Date>这样的标识会有以上的桥方法替程序员正确的处理多态。
即编译器将泛型方法进行类型擦除,并调用setSecond(Object)桥方法,在桥方法内会根据类型来转换来调用正确的setSecond(Date)方法。


当然这里还有第二个问题,如果调用的不是setSecond而是getSecond,如果桥方法同样合成getSecond会出现如下情况:

Date getSecond()
Object getSecond()

这样的情况对于编程人员来说通常是不允许的,即通过返回类型来标识不同的方法,但是对于虚拟机来说确实可以的。所以以上方法可以正常运行。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐