类型擦除
Java在语法中虽然存在泛型的概念,可是在虚拟机中却没有泛型的概念,虚拟机中全部的类型都是普通类。不管什么时候定义一个泛型类型,编译后类型会被都被自动转换成一个相应的原始类型。java
好比这个类程序员
public class Parent<T> {
public void sayHello(T value) {
System.out.println("This is Parent Class, value is " + value);
}
}
在编译后就变成了markdown
public class Parent {
public void sayHello(Object value) {
System.out.println("This is Parent Class, value is " + value);
}
}
对类型变量进行替换的规则有两条:函数
- 若为无限定的类型,如
<T>
,被替换为Object
- 若为限定类型,如
<T extends Comparable & Serializable>
,则用第一个限定的类型变量来替换,在这里被替换为Comparable
桥方法
类型擦除后,就产生了一个奇怪的现象。post
假设有一个超类:测试
public class Parent<T> {
public void sayHello(T value) {
System.out.println("This is Parent Class, value is " + value);
}
}
以及一个子类:this
public class Child extends Parent<String> {
public void sayHello(String value) {
System.out.println("This is Child class, value is " + value);
}
}
最后有如下测试代码,企图实现多态:spa
public class MainApp {
public static void main(String[] args) {
Child child = new Child();
Parent<String> parent = child;
parent.sayHello("This is a string");
}
}
运行的时候,会对Child
类的方法表进行搜索,先分析一下Child
类的方法表里有哪些东西:code
1. sayHello(Object value) : 从类型被擦除后的超类中继承过来
2. sayHello(String value) : 本身新增的方法,和超类毫无联系
3. 一些从Object类继承来的方法,这里忽略
按理来讲,这段测试代码应该不能经过编译,由于要实现多态的话,所调用的方法必须在子类中重写,可是在这里Child
类并无重写Parent
类中的sayHello(Object value)
方法,只是单纯的继承而已,而且新加了一个参数不一样的同名方法。对象
【本身补充理解】
- sayHello(Object value)和sayHello(String value)是两个彻底不一样的签名方法,没有任何关系。可是若是没有sayHello(String)则调用sayHello("this is string")时则会调用sayHello(Object)签名的方法。
- 原文中说不能经过编译,实际上应该是能够的,只是再也不是多态而已。子类没有重写父类方法,指向子类对象的父类引用依然能够调用父类本身的方法,只是此时不叫多态。
【本身补充理解完毕】
可是结果是能够正常运行。
缘由是编译器在Child
类中自动生成了一个桥方法:
public void sayHello(Object value) {
sayHello((String) value);
}
能够看出,这个桥方法实际上就是对超类中sayHello(Obejct)
的重写。这样作的缘由是,当程序员在子类中写下如下这段代码的时候,本意是对超类中的同名方法进行重写,但由于超类发生了类型擦除,因此实际上并无重写成功,所以加入了桥方法的机制来避免类型擦除与多态发生冲突。
public class Child extends Parent<String> {
public void sayHello(String value) {
System.out.println("This is Child class, value is " + value);
}
}
桥方法并不须要本身手动生成,一切都是编译器自动完成的。
桥方法与Geter
一样的,若是超类中有getter
的话,在使用多态的时候也可能发生冲突。假设有超类被类型擦除后存在这样一个方法:
Obejct getValue()
而后在子类中,程序员想要重写这个方法,所以新增了一个这样的方法:
String getValue()
可是正如前面所述,重写并无起做用,甚至还应该报错,由于在子类中,根据 函数签名=方法名+参数 的原则,从超类继承的方法与新增的方法冲突了。
但实际上这样的代码是能够工做的,缘由在于,JVM是用返回值+方法名+参数的方式来计算函数签名的,因此编译器就能够借助这一原则来生成一个桥方法。不过这种计算函数签名的方法仅仅存在于虚拟机中。