之前的文章,我们介绍了Java泛型的基本使用,这里我们将深入到编译期、虚拟机层面当中去。具体地,将会分析介绍类型擦除、泛型翻译方面的内容
类型擦除
在Java虚拟机JVM中是没有泛型这一说的,Java的泛型在编译期会被去除,即所谓的类型擦除。擦除后类型变量将由raw type原始类型来代替,具体地,如果泛型的类型变量上有类型限定,则第一个限定类型即为raw type,否则raw type为Object
1 | /** |
这里,我们对Pair类的Class文件反编译,由于Pair的类型变量T上没有类型限定,则类型擦除后该类型变量T将由Object代替,从下图红框我们也可以看到该类的first、second属性已经由T替换为Object了;而泛型方法minmax的类型变量T由于类型限定,故将使用第一个限定类型Comparable来作为raw type进行替换
泛型翻译
由于Java的类型擦除机制,使得泛型类在编译后可以认为就是一个普通的Java类,那如何保证类型正确呢?比如这里testPair1方法中getFirst、getSecond,由于类型擦除机制的存在,其应该返回的是Object类型,讲道理,不应该允许直接赋给String类型的变量啊
1 | // 测试泛型类 |
其实,道理很简单,虽然字节码中的类型是Object了,但是编译器在调用这些方法的同时,会适当的自动的插入强制转换的指令,我们对该方法进行反编译,即可看到checkcast字节码指令
现在我们来介绍另外泛型翻译的情形——Bridge Method 桥方法。这里,提供了一个Pair的继承类
1 | public class NumPair extends Pair<Integer> { |
很明显,NumPair的setFirst方法是重写了父类的setFirst方法。但是由于类型擦除机制的存在,父类的setFirst方法的参数first其类型由T已经变为了Object,这一点从反编译后的结果也可以看出来。那么现在问题就来了,父类的方法签名是setFirst(Object first),而子类中该方法签名是setFirst(Integer first),二者不一样。目前看上去,类型擦除机制好像是与多态发生了冲突
我们先测试下,看看类型擦除机制与多态是否存在冲突
1 | public class NumPairTest { |
通过打印输出,我们可以确定子类的setFirst被正确地调用了,咦?多态与类型擦除机制二者之间好像又没有冲突了,而是父类与子类的setFirst方法之间签名确实不一样啊,这到底是怎么回事呢?
其实道理很简单,还是老办法,我们对子类的字节码文件进行反编译来一探究竟,从下图可以看出,编译器不仅生成我们所提供的setFirst(Integer first)方法,还帮我们自动生成了一个签名为setFirst(Object first)的新方法(如下图蓝框所示)。其实确实是由于类型擦除的原因,导致我们重写方法与父类方法在编译之后出现了签名不一致的情况。编译器为了解决这个冲突,使得多态特性不被破坏。其会自动生成一个与父类签名一致的方法setFirst(Object first),并在其内部去调用我们期望的setFirst(Integer first)方法。由于这个编译器自动生成的方法,一方面是负责来实际重写父类方法的,另一方面则是为了调用开发者实际提供的重写方法,故其被形象地称之为Bridge Method 桥方法
参考文献
- Java核心技术·卷I 凯.S.霍斯特曼著