前言
最近在复习泛型时,看到一个关于泛型的多态的知识点。感觉挺有意思的,在这里记录一下。
问题引入
我们都知道,泛型是作用于编译期,是为了在编译期保证类型约束与提供类型安全型的,在编译过程泛型会被擦除,运行期不会得到对应的泛型信息。对于无限制的泛型参数,擦除后会被直接替换成Object;对于有限制的泛型参数,会被替换成上下界上界,比如<T extends Number>
和<? extends Number>
会被替换成上界Number,<? super Number>
会被替换成上界Object。
问题就从这里来了,泛型擦除会导致多态出现问题。比如有一个这样的父类和其子类:
public class Message<T> {
public T get(T var){
return var;
}
}
class SmsMessage extends Message<Integer> {
@Override
public Integer get(Integer var) {
return var;
}
}
经过泛型擦除之后,按照理论来说将会得到下面这样的结果:
public class Message<Object> {
public Object get(Object var){
return var;
}
}
public class SmsMessage extends Message<Integer> {
@Override
public Integer get(Integer var) {
return var;
}
}
可以看到,父类和其子类定义的参数和返回值将会不一样,这样两者就不是Override(重写)的关系了,而应该是Overload(重载)关系。
而多态的定义是:同一个行为具有多个不同表现形式或形态的能力,多态有三个的必要条件:
1、要有继承
2、要有override(重写)
3、父类引用指向子类对象
可见如果不能实现重写将会导致多态不能实现。
解决办法
JVM采用了一个叫做桥接的特殊方法解决类型擦除和多态的冲突问题。
通过javap命令对编译后的SmsMessage.class文件进行反编译,得到下面的反编译后的结果:
class SmsMessage extends Message<java.lang.Integer> {
SmsMessage();
public java.lang.Integer get(java.lang.Integer);
public java.lang.Object get(java.lang.Object);
}
可以看到,编译后的类中其实存在两个get方法,分别对应于上文所说的父类、子类的get方法,而第二个get方法其实就是所谓的桥接方法。我们进一步用javap -c反编译得到字节码:
class SmsMessage extends Message<java.lang.Integer> {
SmsMessage();
Code:
0: aload_0
1: invokespecial #1 // Method Message."<init>":()V
4: return
public java.lang.Integer get(java.lang.Integer);
Code:
0: aload_1
1: areturn
public java.lang.Object get(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #7 // class java/lang/Integer
5: invokevirtual #9 // Method get:(Ljava/lang/Integer;)Ljava/lang/Integer;
8: areturn
}
可以看到,在第二个get方法的5: invokevirtual #9
处,调用了第一个get方法,这个桥接方法就搭建起了父类和子类间的桥梁,使得多态与泛型擦除不会存在冲突问题了。