Java中的類型擦除與橋方法
類型擦除
Java在語(yǔ)法中雖然存在泛型的概念,但是在虛擬機(jī)中卻沒(méi)有泛型的概念,虛擬機(jī)中所有的類型都是普通類。無(wú)論何時(shí)定義一個(gè)泛型類型,編譯后類型會(huì)被都被自動(dòng)轉(zhuǎn)換成一個(gè)相應(yīng)的原始類型。
比如這個(gè)類
public class Parent<T>
{
public void sayHello(T value)
{
System.out.println("This is Parent Class, value is " + value);
}
}
在編譯后就變成了
public class Parent
{
public void sayHello(Object value)
{
System.out.println("This is Parent Class, value is " + value);
}
}
對(duì)類型變量進(jìn)行替換的規(guī)則有兩條:
- 若為無(wú)限定的類型,如
<T>,被替換為Object - 若為限定類型,如
<T extends Comparable & Serializable>,則用第一個(gè)限定的類型變量來(lái)替換,在這里被替換為Comparable
橋方法
類型擦除后,就產(chǎn)生了一個(gè)奇怪的現(xiàn)象。
假設(shè)有一個(gè)超類:
public class Parent<T>
{
public void sayHello(T value)
{
System.out.println("This is Parent Class, value is " + value);
}
}
以及一個(gè)子類:
public class Child extends Parent<String>
{
public void sayHello(String value)
{
System.out.println("This is Child class, value is " + value);
}
}
最后有以下測(cè)試代碼,企圖實(shí)現(xiàn)多態(tài):
public class MainApp
{
public static void main(String[] args)
{
Child child = new Child();
Parent<String> parent = child;
parent.sayHello("This is a string");
}
}
運(yùn)行的時(shí)候,會(huì)對(duì)Child類的方法表進(jìn)行搜索,先分析一下Child類的方法表里有哪些東西:
1. sayHello(Object value) : 從類型被擦除后的超類中繼承過(guò)來(lái)
2. sayHello(String value) : 自己新增的方法,和超類毫無(wú)聯(lián)系
3. 一些從Object類繼承來(lái)的方法,這里忽略
按理來(lái)說(shuō),這段測(cè)試代碼應(yīng)該不能通過(guò)編譯,因?yàn)橐獙?shí)現(xiàn)多態(tài)的話,所調(diào)用的方法必須在子類中重寫,但是在這里Child類并沒(méi)有重寫Parent類中的sayHello(Object value)方法,只是單純的繼承而已,并且新加了一個(gè)參數(shù)不同的同名方法。
【自己補(bǔ)充理解】
- sayHello(Object value)和sayHello(String value)是兩個(gè)完全不同的簽名方法,沒(méi)有任何關(guān)系。但是如果沒(méi)有sayHello(String)則調(diào)用sayHello("this is string")時(shí)則會(huì)調(diào)用sayHello(Object)簽名的方法。
- 原文中說(shuō)不能通過(guò)編譯,實(shí)際上應(yīng)該是可以的,只是不再是多態(tài)而已。子類沒(méi)有重寫父類方法,指向子類對(duì)象的父類引用依然可以調(diào)用父類自己的方法,只是此時(shí)不叫多態(tài)。
【自己補(bǔ)充理解完畢】
但是結(jié)果是可以正常運(yùn)行。
原因是編譯器在Child類中自動(dòng)生成了一個(gè)橋方法:
public void sayHello(Object value)
{
sayHello((String) value);
}
可以看出,這個(gè)橋方法實(shí)際上就是對(duì)超類中sayHello(Obejct)的重寫。這樣做的原因是,當(dāng)程序員在子類中寫下以下這段代碼的時(shí)候,本意是對(duì)超類中的同名方法進(jìn)行重寫,但因?yàn)槌惏l(fā)生了類型擦除,所以實(shí)際上并沒(méi)有重寫成功,因此加入了橋方法的機(jī)制來(lái)避免類型擦除與多態(tài)發(fā)生沖突。
public class Child extends Parent<String>
{
public void sayHello(String value)
{
System.out.println("This is Child class, value is " + value);
}
}
橋方法并不需要自己手動(dòng)生成,一切都是編譯器自動(dòng)完成的。
橋方法與Geter
同樣的,如果超類中有getter的話,在使用多態(tài)的時(shí)候也可能發(fā)生沖突。假設(shè)有超類被類型擦除后存在這樣一個(gè)方法:
Obejct getValue()
然后在子類中,程序員想要重寫這個(gè)方法,因此新增了一個(gè)這樣的方法:
String getValue()
但是正如前面所述,重寫并沒(méi)有起作用,甚至還應(yīng)該報(bào)錯(cuò),因?yàn)樵谧宇愔校鶕?jù) 函數(shù)簽名=方法名+參數(shù) 的原則,從超類繼承的方法與新增的方法沖突了。
但實(shí)際上這樣的代碼是可以工作的,原因在于,JVM是用返回值+方法名+參數(shù)的方式來(lái)計(jì)算函數(shù)簽名的,所以編譯器就可以借助這一原則來(lái)生成一個(gè)橋方法。不過(guò)這種計(jì)算函數(shù)簽名的方法僅僅存在于虛擬機(jī)中。
posted on 2019-09-22 18:46 everest33 閱讀(212) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)