java泛型擦除與橋方法


Java在語(yǔ)法中雖然存在泛型的概念,但是在虛擬機(jī)中卻沒有泛型的概念,虛擬機(jī)中所有的類型都是普通類。無論何時(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ī)則有兩條:

  • 若為無限定的類型,如<T>,被替換為Object
  • 若為限定類型,如<T extends Comparable & Serializable>,則用第一個(gè)限定的類型變量來替換,在這里被替換為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) : 從類型被擦除后的超類中繼承過來
  2. sayHello(String value) : 自己新增的方法,和超類毫無聯(lián)系
  3. 一些從Object類繼承來的方法,這里忽略

按理來說,這段測(cè)試代碼應(yīng)該不能通過編譯,因?yàn)橐獙?shí)現(xiàn)多態(tài)的話,所調(diào)用的方法必須在子類中重寫,但是在這里Child類并沒有重寫Parent類中的sayHello(Object value)方法,只是單純的繼承而已,并且新加了一個(gè)參數(shù)不同的同名方法。

但是結(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í)際上并沒有重寫成功,因此加入了橋方法的機(jī)制來避免類型擦除與多態(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

同樣的,如果超類中有g(shù)etter的話,在使用多態(tài)的時(shí)候也可能發(fā)生沖突。假設(shè)有超類被類型擦除后存在這樣一個(gè)方法:

Obejct getValue()

然后在子類中,程序員想要重寫這個(gè)方法,因此新增了一個(gè)這樣的方法:

String getValue()

但是正如前面所述,重寫并沒有起作用,甚至還應(yīng)該報(bào)錯(cuò),因?yàn)樵谧宇愔?,根?jù) 函數(shù)簽名=方法名+參數(shù) 的原則,從超類繼承的方法與新增的方法沖突了。

但實(shí)際上這樣的代碼是可以工作的,原因在于,JVM是用返回值+方法名+參數(shù)的方式來計(jì)算函數(shù)簽名的,所以編譯器就可以借助這一原則來生成一個(gè)橋方法。不過這種計(jì)算函數(shù)簽名的方法僅僅存在于虛擬機(jī)中。

實(shí)踐才是真道理,接下來,實(shí)際驗(yàn)證下上面的demo,這里我們只需要看child類是否像我們預(yù)測(cè)的一樣;
工具:idea ,插件:ASM Bytecode Outline


image.png
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容