欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java的"偽泛型"變"真泛型"后對(duì)性能的影響

 更新時(shí)間:2025年05月07日 08:46:38   作者:鳩摩(馬智)  
泛型擦除本質(zhì)上就是擦除與泛型相關(guān)的一切信息,例如參數(shù)化類型、類型變量等,Javac還將在需要時(shí)進(jìn)行類型檢查及強(qiáng)制類型轉(zhuǎn)換,甚至在必要時(shí)會(huì)合成橋方法,這篇文章主要介紹了Java的"偽泛型"變"真泛型"后對(duì)性能的影響,需要的朋友可以參考下

泛型存在于Java源代碼中,在編譯為字節(jié)碼文件之前都會(huì)進(jìn)行泛型擦除(type erasure),因此,Java的泛型完全由Javac等編譯器在編譯期提供支持,可以理解為Java的一顆語法糖,這種方式實(shí)現(xiàn)的泛型有時(shí)也稱為“偽泛型”。

泛型擦除本質(zhì)上就是擦除與泛型相關(guān)的一切信息,例如參數(shù)化類型、類型變量等,Javac還將在需要時(shí)進(jìn)行類型檢查及強(qiáng)制類型轉(zhuǎn)換,甚至在必要時(shí)會(huì)合成橋方法。

1、真假泛型

如果你是Java業(yè)務(wù)開發(fā)者,其實(shí)這種所謂的偽泛型已經(jīng)達(dá)到了方便使用的目的,例如在容器的使用過程中,能夠記住其類型,這樣就不用在獲取時(shí)專門做強(qiáng)制類型轉(zhuǎn)換了,如下:

package cn.hotspotvm;
class Wrapper<T> {
    public T data;
    public void setData(T data) {
        this.data = data;
    }
    public T getData(){
        return data;
    }
}
public class TestGeneric {
    public static void main(String[] args) {
        Wrapper<Integer> p = new Wrapper<>();
        p.setData(new Integer(2));
        // 在獲取的時(shí)候不用進(jìn)行強(qiáng)制類型轉(zhuǎn)換,直接用
        // Integer接收即可
        Integer data = p.getData();
        System.out.println(data);
    }
}

泛型尤其在我們使用容器類,如ArrayList、 HashMap等時(shí)能提供便利。

假設(shè)Java不支持泛型,那么我們針對(duì)Integer類型進(jìn)行封裝的Wrapper代碼應(yīng)該是如下的樣子:

class Wrapper{
 public int data;
 public void setData(int data) {
   this.data = data;
 }
 public Object getData(){
   return data;
 }
}
public class TestGeneric {
    public static void main(String[] args) {
        Wrapper p = new Wrapper();
        p.setData(2);
        int data = p.getData();
        System.out.println(data);
    }
}

這個(gè)Wrapper明顯是只能對(duì)int類型進(jìn)行封裝,不過其實(shí)現(xiàn)和之前比起來要簡(jiǎn)潔,不但實(shí)例字段內(nèi)存占用減少,還沒有了裝箱和拆箱操作,也省去了強(qiáng)制類型轉(zhuǎn)換。這也是我們?cè)谑褂肑ava泛型時(shí)希望看到的版本。

以目前Java的實(shí)現(xiàn)來看,是對(duì)泛型進(jìn)行擦除處理的,對(duì)Wrapper類型進(jìn)行擦除后的代碼如下所示。

class Wrapper{
    public Object data;
    public void setData(Object data) {
        this.data = data;
    }
    public Object getData(){
        return data;
    }
}
public class TestGeneric {
    public static void main(String[] args) {
        Wrapper p = new Wrapper();
        p.setData(2);
        // 必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換為Integer
        Integer data = (Integer)p.getData();
        System.out.println(data);
    }
}

由于在整個(gè)應(yīng)用中,無論是Wrapper還是Wrapper這些類型來說,其Wrapper在虛擬機(jī)中只有一個(gè)版本,因?yàn)樾枰獙?duì)任何的Java對(duì)象進(jìn)行封裝,所以聲明為Object,當(dāng)然如果你知道只會(huì)放某個(gè)更具體的類或這個(gè)類的子類時(shí),也可以將Wrapper類型聲明的更精確一些,如Wrapper。

假設(shè)像C++的模板類一樣,Java的“真泛型”也為每一個(gè)具體的泛型生成一個(gè)獨(dú)有的類(類型膨脹式泛型),那么就如下圖所示。

可以看到,真泛型會(huì)針對(duì)具體的類型生成獨(dú)有的類型。針對(duì)Wrapper就應(yīng)該有是這樣:

class Wrapper{
    public Integer data;
    public void setData(Integer data) {
        this.data = data;
    }
    public Integer getData(){
        return data;
    }
}
public class TestGeneric {
    public static void main(String[] args) {
        Wrapper p = new Wrapper();
        p.setData(2);
        Integer data = p.getData();
        System.out.println(data);
    }
}

這次類型非常精確,也沒有了強(qiáng)制類型轉(zhuǎn)換。不過與我們理想中的還有差距,主要是沒有將Wrapper中的類型聲明為基本類型int,這會(huì)導(dǎo)致裝箱和拆箱操作。在Java中,裝箱和拆箱操作很頻繁,為此JDK開發(fā)人員也在努力優(yōu)化,如延遲裝箱等,現(xiàn)在,Project Valhalla要讓泛型能支持原始類型。好在除非是專門做優(yōu)化的人,否則一般開發(fā)者也不會(huì)注意到這種裝箱和拆箱的開銷。

這里還要說的是,由于對(duì)象和基本類型找不到一個(gè)共同的父類,所以在泛型擦除時(shí)只能是對(duì)象類型,也就是說,我們不能在Java中寫一個(gè)類似Wrapper這樣的聲明。這篇文章我們暫時(shí)不討論這個(gè)問題,我們討論另外一個(gè)重要的問題,也就是為任何一個(gè)泛型生成一個(gè)真正的類與這種偽泛型的擦除之間會(huì)造成哪些性能影響?

2、性能影響

我們舉幾個(gè)小例子來看一下:

實(shí)例1

class SpecWrapper extends Wrapper<Integer> {
    public void setData(Integer data) { }
}

我們自定義了一個(gè)對(duì)Integer類封裝的SpecWrapper類,這個(gè)類在泛型擦除后如下所示。

class SpecWrapper extends Wrapper {
    public void setData(Integer data) { }
    /*synthetic*/ public void setData(Object x0) { // 合成的橋方法
        this.setData((Integer)x0);
    }
}

在泛型擦除后,Wrapper類的setData()方法的類型變量T被替換為Object類型,這樣SpecWrapper類中的setData(Integer data)并沒有覆寫這個(gè)方法,所以為了覆寫特性,向SpecWrapper類中添加一個(gè)合成的橋方法setData(Objext x0)。

這會(huì)讓我們?cè)趯?shí)際調(diào)用setData()方法時(shí)調(diào)用的是setData(Object x0)方法,這個(gè)方法又調(diào)用了setData(Integer data)方法,而在“真泛型”中,我們直接調(diào)用setData(Integer data)方法即可,雖然JIT會(huì)大概率對(duì)這種簡(jiǎn)單的方法進(jìn)行內(nèi)聯(lián),但是性能影響肯定是有的。

實(shí)例2

class Parent {
    public static int a = 0;
    public void invoke() {
       a = 1;
    }
}
final class Sub1 extends Parent {
    public void invoke() {
        a = 2;
    }
}
public class TestGeneric<T extends Parent> {
    T t;
    public TestGeneric(T t1) {
        this.t = t1;
    }
    public static void main(String[] args)
          throws InterruptedException {
        TestGeneric<Sub1> s
                 = new TestGeneric<>(new Sub1());
        // 調(diào)用test()方法超過一定次數(shù)會(huì)分別觸發(fā)C1和C2編譯
        for (int i = 0; i < 100000; i++) {
            s.test();
        }
        // 等待異常的編譯線程編譯完并打印結(jié)果
        Thread.sleep(10000);
    }
    public void test() {
        t.invoke();
    }
}

我們關(guān)注一下test()方法中的t.invoke()動(dòng)態(tài)分派,通過泛型擦除之后,TestGeneric類中的T是Parent類型,那么test()方法中t聲明的類型就是Parent,如下:

public class TestGeneric{
    Parent t;
    public TestGeneric(Parent t1) {
        this.t = t1;
    }
    public void test() {
        t.invoke();
    }
}

配置如下命令查看C2編譯的結(jié)果:

XX:CompileCommand=compileonly,cn/hotspotvm/TestGeneric::test -XX:CompileCommand=print,cn/hotspotvm/TestGeneric::test

C2編譯的版本出來如下:

  0x000070e6e00aa915: cmp    $0xf800c184,%r8d   ;   {metadata('cn/hotspotvm/Sub1')}
  0x000070e6e00aa91c: jne    0x000070e6e00aa934
  0x000070e6e00aa91e: lea    (%r12,%r10,8),%rsi
  0x000070e6e00aa922: nop
  0x000070e6e00aa923: callq  0x000070e6dfe45e60  ; OopMap{off=72}
                                                ;*invokevirtual invoke
                                                ; - cn.hotspotvm.TestGeneric::test@4 (line 39)
                                                ;   {optimized virtual_call}
  0x000070e6e00aa928: add    $0x10,%rsp
  0x000070e6e00aa92c: pop    %rbp
  0x000070e6e00aa92d: test   %eax,0x165cb6cd(%rip)        # 0x000070e6f6676000
                                                ;   {poll_return}
  0x000070e6e00aa933: retq
  0x000070e6e00aa934: mov    $0xffffffde,%esi
  0x000070e6e00aa939: mov    %r10d,%ebp
  0x000070e6e00aa93c: data32 xchg %ax,%ax
  0x000070e6e00aa93f: callq  0x000070e6dfe45460  ; OopMap{rbp=NarrowOop off=100}
                                                ;*invokevirtual invoke
                                                ; - cn.hotspotvm.TestGeneric::test@4 (line 39)
                                                ;   {runtime_call}

也就等同于如下:

if(t是Sub1類型){
   直接調(diào)用Sub1的invoke()方法,也就是optimized virtual_call的意思
}else{
   動(dòng)態(tài)分派,通過查找方法表來實(shí)現(xiàn)調(diào)用,也就是invokevirtual invoke的意思
}

C2實(shí)際是通過運(yùn)行時(shí)采樣,發(fā)現(xiàn)t的類型只有Sub1,所以做了這樣的優(yōu)化,將動(dòng)態(tài)分派優(yōu)化為了一次比較+一次直接調(diào)用的開銷?! ?/p>

假設(shè)是“真泛型”上場(chǎng),那TestGeneric應(yīng)該是如下的樣子:

public class TestGeneric{
    Sub1 t;
    public TestGeneric(Sub1 t1) {
        this.t = t1;
    }
    public void test() {
        t.invoke();
    }
}

此時(shí)C2編譯出來的版本如下:

  0x000074bd840b7b5b: callq  0x000074bd83e45e60  ; OopMap{off=64}
                                                ;*invokevirtual invoke
                                                ; - cn.hotspotvm.TestGeneric::test@4 (line 39)
                                                ;   {optimized virtual_call}

直接就是直調(diào),比之前省了一次判斷,代碼很簡(jiǎn)潔。因?yàn)镃2得到了t的更精確類型Sub1,并且這個(gè)Sub1還是final修飾的類,不會(huì)有子類,所以直接調(diào)用即可。

雖然少量調(diào)用可能并不能體現(xiàn)出來差異,更何況現(xiàn)在的C2優(yōu)化實(shí)在強(qiáng)大,使得它們的性能差異可能只在極少數(shù)的情況下才能體現(xiàn)出來。

C2編譯器非常喜歡精確類型,這樣在類型傳播的過程中能觸發(fā)許多優(yōu)化,編譯出性能更好的版本,后續(xù)我們?cè)诮榻BC2時(shí),看一看其類型傳播,就能深刻體會(huì)到它的強(qiáng)大?!?/p>

實(shí)例3

假設(shè)有這么一個(gè)方法,實(shí)現(xiàn)如下:

class Parent{
  // ...
}
find class Sub1 extends Parent{
  // ...
}
class Sub2 extends Parent{
  // ...
}
public class Wrapper<T extends Parent>{
 public void  test(T t){
    if(t instanceof Sub1){
        // 執(zhí)行Sub1的邏輯
    }else if( t instanceof Sub2){
        // 執(zhí)行Sub2的邏輯
    }else{
        // 執(zhí)行其它類型的邏輯 
    }
 }
}

對(duì)于偽泛型來說,擦除后,T是Parent類型。方法test(Parent t)無法做任何優(yōu)化。

對(duì)于真泛型來說,至少Wrapper和Wrapper版本中的test()是可以優(yōu)化的。

public void test(Sub1 t)版本中只需要直接執(zhí)行Sub1的邏輯就可以,并不需要判斷,因?yàn)镾ub1是final類,所以t只能是Sub1類型,如下:

public void test(Sub1 t){
   執(zhí)行Sub1的邏輯
}

public void test(Sub2 t)版本中只需要直接執(zhí)行Sub2的邏輯就可以,并不需要判斷,雖然Sub2是非final類型,但是經(jīng)過類層次分析后發(fā)現(xiàn),Sub2沒有具體的實(shí)現(xiàn)子類,那這時(shí)候也能認(rèn)為這個(gè)版本只有Sub2類型。

public void test(Sub2 t){
   執(zhí)行Sub2的邏輯
}

到此這篇關(guān)于Java的"偽泛型"變"真泛型"后,會(huì)對(duì)性能有幫助嗎?的文章就介紹到這了,更多相關(guān)Java的"偽泛型"變"真泛型"后,會(huì)對(duì)性能有幫助嗎??jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • Java 泛型總結(jié)及詳解

    Java 泛型總結(jié)及詳解

    這篇文章主要介紹了Java 泛型的相關(guān)資料,并附簡(jiǎn)單實(shí)例代碼,需要的朋友可以參考下
    2016-09-09
  • Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例

    Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例

    這篇文章主要介紹了Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例,其中作者創(chuàng)建的Fraction類可以用來進(jìn)行各種分?jǐn)?shù)運(yùn)算,需要的朋友可以參考下
    2015-09-09
  • JAVA異常和自定義異常處理方式

    JAVA異常和自定義異常處理方式

    這篇文章主要介紹了JAVA異常和自定義異常處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java提示缺少返回語句的解決辦法

    Java提示缺少返回語句的解決辦法

    在本篇文章里小編給大家分享了關(guān)于Java提示缺少返回語句的解決辦法以及相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。
    2019-07-07
  • Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String,T>的操作

    Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String,T>的操作

    這篇文章主要介紹了Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String, T>的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 數(shù)組在java中的擴(kuò)容的實(shí)例方法

    數(shù)組在java中的擴(kuò)容的實(shí)例方法

    在本篇文章里小編給大家分享的是一篇關(guān)于數(shù)組在java中的擴(kuò)容的實(shí)例方法內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2021-01-01
  • SpringBoot中熱部署配置深入講解原理

    SpringBoot中熱部署配置深入講解原理

    在實(shí)際開發(fā)中,每次修改代碼就需要重啟項(xiàng)目,重新部署,對(duì)于一個(gè)后端開發(fā)者來說,重啟確實(shí)很難受。在java開發(fā)領(lǐng)域,熱部署一直是一個(gè)難以解決的問題,目前java虛擬機(jī)只能實(shí)現(xiàn)方法體的熱部署,對(duì)于整個(gè)類的結(jié)構(gòu)修改,仍然需要重啟項(xiàng)目
    2023-01-01
  • java防反編譯最簡(jiǎn)單的技巧分享

    java防反編譯最簡(jiǎn)單的技巧分享

    這篇文章主要給大家分享了關(guān)于java防反編譯最簡(jiǎn)單的技巧,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09
  • 使用mybatis-plus想要修改某字段為null問題

    使用mybatis-plus想要修改某字段為null問題

    這篇文章主要介紹了使用mybatis-plus想要修改某字段為null問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • SpringBoot接口加密與解密的實(shí)現(xiàn)

    SpringBoot接口加密與解密的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot接口加密與解密的實(shí)現(xiàn)
    2023-10-10

最新評(píng)論