成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

聊聊Java的泛型及實(shí)現(xiàn)

lewif / 817人閱讀

摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。

前言

和C++以模板來(lái)實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得自己泛型參數(shù)的Class類型,C++只能由編譯器推斷在不為人知的地方生成新的類,對(duì)于特定的模板參數(shù)你只能使用特化。在本文中我主要想聊聊泛型的實(shí)現(xiàn)原理和一些高級(jí)特性。

泛型基礎(chǔ)

泛型是對(duì)Java語(yǔ)言類型系統(tǒng)的一種擴(kuò)展,有點(diǎn)類似于C++的模板,可以把類型參數(shù)看作是使用參數(shù)化類型時(shí)指定的類型的一個(gè)占位符。引入泛型,是對(duì)Java語(yǔ)言一個(gè)較大的功能增強(qiáng),帶來(lái)了很多的好處:

類型安全。類型錯(cuò)誤現(xiàn)在在編譯期間就被捕獲到了,而不是在運(yùn)行時(shí)當(dāng)作java.lang.ClassCastException展示出來(lái),將類型檢查從運(yùn)行時(shí)挪到編譯時(shí)有助于開發(fā)者更容易找到錯(cuò)誤,并提高程序的可靠性

消除了代碼中許多的強(qiáng)制類型轉(zhuǎn)換,增強(qiáng)了代碼的可讀性

為較大的優(yōu)化帶來(lái)了可能

泛型是什么并不會(huì)對(duì)一個(gè)對(duì)象實(shí)例是什么類型的造成影響,所以,通過(guò)改變泛型的方式試圖定義不同的重載方法是不可以的。剩下的內(nèi)容我不會(huì)對(duì)泛型的使用做過(guò)多的講述,泛型的通配符等知識(shí)請(qǐng)自行查閱。

在進(jìn)入下面的論述之前我想先問(wèn)幾個(gè)問(wèn)題:

定義一個(gè)泛型類最后到底會(huì)生成幾個(gè)類,比如ArrayList到底有幾個(gè)類

定義一個(gè)泛型方法最終會(huì)有幾個(gè)方法在class文件中

為什么泛型參數(shù)不能是基本類型呢

ArrayList是一個(gè)類嗎

ArrayList和List和ArrayList和List是什么關(guān)系呢,這幾個(gè)類型的引用能相互賦值嗎

類型擦除

正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型基本上都是在編譯器這個(gè)層次來(lái)實(shí)現(xiàn)的。在生成的Java字節(jié)代碼中是不包含泛型中的類型信息的。使用泛型的時(shí)候加上的類型參數(shù),會(huì)被編譯器在編譯的時(shí)候去掉。這個(gè)過(guò)程就稱為類型擦除。如在代碼中定義的List和List等類型,在編譯之后都會(huì)變成List。JVM看到的只是List,而由泛型附加的類型信息對(duì)JVM來(lái)說(shuō)是不可見的。Java編譯器會(huì)在編譯時(shí)盡可能的發(fā)現(xiàn)可能出錯(cuò)的地方,但是仍然無(wú)法避免在運(yùn)行時(shí)刻出現(xiàn)類型轉(zhuǎn)換異常的情況。類型擦除也是Java的泛型實(shí)現(xiàn)方式與C++模板機(jī)制實(shí)現(xiàn)方式之間的重要區(qū)別。

很多泛型的奇怪特性都與這個(gè)類型擦除的存在有關(guān),包括:

泛型類并沒有自己獨(dú)有的Class類對(duì)象。比如并不存在List.class或是List.class,而只有List.class。

靜態(tài)變量是被泛型類的所有實(shí)例所共享的。對(duì)于聲明為MyClass的類,訪問(wèn)其中的靜態(tài)變量的方法仍然是 MyClass.myStaticVar。不管是通過(guò)new MyClass還是new MyClass創(chuàng)建的對(duì)象,都是共享一個(gè)靜態(tài)變量。

泛型的類型參數(shù)不能用在Java異常處理的catch語(yǔ)句中。因?yàn)楫惓L幚硎怯蒍VM在運(yùn)行時(shí)刻來(lái)進(jìn)行的。由于類型信息被擦除,JVM是無(wú)法區(qū)分兩個(gè)異常類型MyException和MyException的。對(duì)于JVM來(lái)說(shuō),它們都是 MyException類型的。也就無(wú)法執(zhí)行與異常對(duì)應(yīng)的catch語(yǔ)句。

類型擦除的基本過(guò)程也比較簡(jiǎn)單,首先是找到用來(lái)替換類型參數(shù)的具體類。這個(gè)具體類一般是Object。如果指定了類型參數(shù)的上界的話,則使用這個(gè)上界。把代碼中的類型參數(shù)都替換成具體的類。同時(shí)去掉出現(xiàn)的類型聲明,即去掉<>的內(nèi)容。比如T get()方法聲明就變成了Object get();List就變成了List。

泛型的實(shí)現(xiàn)原理

因?yàn)榉N種原因,Java不能實(shí)現(xiàn)真正的泛型,只能使用類型擦除來(lái)實(shí)現(xiàn)偽泛型,這樣雖然不會(huì)有類型膨脹(C++模板令人困擾的難題)的問(wèn)題,但是也引起了許多新的問(wèn)題。所以,Sun對(duì)這些問(wèn)題作出了許多限制,避免我們犯各種錯(cuò)誤。

保證類型安全

首先第一個(gè)是泛型所宣稱的類型安全,既然類型擦除了,如何保證我們只能使用泛型變量限定的類型呢?java編譯器是通過(guò)先檢查代碼中泛型的類型,然后再進(jìn)行類型擦除,在進(jìn)行編譯的。那類型檢查是針對(duì)誰(shuí)的呢,讓我們先看一個(gè)例子。

ArrayList arrayList1=new ArrayList(); // 正確,只能放入String
ArrayList arrayList2=new ArrayList(); // 可以放入任意Object

這樣是沒有錯(cuò)誤的,不過(guò)會(huì)有個(gè)編譯時(shí)警告。不過(guò)在第一種情況,可以實(shí)現(xiàn)與 完全使用泛型參數(shù)一樣的效果,第二種則完全沒效果。因?yàn)?,本?lái)類型檢查就是編譯時(shí)完成的。new ArrayList()只是在內(nèi)存中開辟一個(gè)存儲(chǔ)空間,可以存儲(chǔ)任何的類型對(duì)象。而真正涉及類型檢查的是它的引用,因?yàn)槲覀兪鞘褂盟胊rrayList1 來(lái)調(diào)用它的方法,比如說(shuō)調(diào)用add()方法。所以arrayList1引用能完成泛型類型的檢查。
而引用arrayList2沒有使用泛型,所以不行。

類型檢查就是針對(duì)引用的,誰(shuí)是一個(gè)引用,用這個(gè)引用調(diào)用泛型方法,就會(huì)對(duì)這個(gè)引用調(diào)用的方法進(jìn)行類型檢測(cè),而無(wú)關(guān)它真正引用的對(duì)象。

實(shí)現(xiàn)自動(dòng)類型轉(zhuǎn)換

因?yàn)轭愋筒脸膯?wèn)題,所以所有的泛型類型變量最后都會(huì)被替換為原始類型。這樣就引起了一個(gè)問(wèn)題,既然都被替換為原始類型,那么為什么我們?cè)讷@取的時(shí)候,不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換呢?

public class Test {  
    public static void main(String[] args) {  
        ArrayList list=new ArrayList();  
        list.add(new Date());  
        Date myDate=list.get(0);
    }      
}  

編譯器生成的class文件中會(huì)在你調(diào)用泛型方法完成之后返回調(diào)用點(diǎn)之前加上類型轉(zhuǎn)換的操作,比如上文的get函數(shù),就是在get方法完成后,jump回原本的賦值操作的指令位置之前加入了強(qiáng)制轉(zhuǎn)換,轉(zhuǎn)換的類型由編譯器推導(dǎo)。

泛型中的繼承關(guān)系

先看一個(gè)例子:


class DateInter extends A {  
    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  
    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}

先來(lái)分析setValue方法,父類的類型是Object,而子類的類型是Date,參數(shù)類型不一樣,這如果實(shí)在普通的繼承關(guān)系中,根本就不會(huì)是重寫,而是重載。

public void setValue(java.util.Date);  //我們重寫的setValue方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                // invoke A setValue
:(Ljava/lang/Object;)V  
       5: return  
  
  public java.util.Date getValue();    //我們重寫的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // A.getValue  
:()Ljava/lang/Object;  
       4: checkcast     #26               
       7: areturn  
  
  public java.lang.Object getValue();     //編譯時(shí)由編譯器生成的方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:() 去調(diào)用我們重寫的getValue方法  
;  
       4: areturn  
  
  public void setValue(java.lang.Object);   //編譯時(shí)由編譯器生成的方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 
       5: invokevirtual #30                 // Method setValue;   去調(diào)用我們重寫的setValue方法  
)V  
       8: return  

并且,還有一點(diǎn)也許會(huì)有疑問(wèn),子類中的方法 Object getValue()和Date getValue()是同 時(shí)存在的,可是如果是常規(guī)的兩個(gè)方法,他們的方法簽名是一樣的,也就是說(shuō)虛擬機(jī)根本不能分別這兩個(gè)方法。如果是我們自己編寫Java代碼,這樣的代碼是無(wú)法通過(guò)編譯器的檢查的,但是虛擬機(jī)卻是允許這樣做的,因?yàn)樘摂M機(jī)通過(guò)參數(shù)類型和返回類型來(lái)確定一個(gè)方法,所以編譯器為了實(shí)現(xiàn)泛型的多態(tài)允許自己做這個(gè)看起來(lái)“不合法”的事情,然后交給虛擬器去區(qū)別。

我們?cè)倏匆粋€(gè)經(jīng)常出現(xiàn)的例子。

class A {
    Object get(){
        return new Object();
    }
}

class B extends A {
    @Override
    Integer get() {
        return new Integer(1);
    }
}

  public static void main(String[] args){
    A a = new B();
    B b = (B) a;
    A c = new A();
    a.get();
    b.get();
    c.get();
  }

反編譯之后的結(jié)果

17: invokespecial #5                  // Method com/suemi/network/test/A."":()V
      20: astore_3
      21: aload_1
      22: invokevirtual #6                  // Method com/suemi/network/test/A.get:()Ljava/lang/Object;
      25: pop
      26: aload_2
      27: invokevirtual #7                  // Method com/suemi/network/test/B.get:()Ljava/lang/Integer;
      30: pop
      31: aload_3
      32: invokevirtual #6                  // Method com/suemi/network/test/A.get:()Ljava/lang/Object;

實(shí)際上當(dāng)我們使用父類引用調(diào)用子類的get時(shí),先調(diào)用的是JVM生成的那個(gè)覆蓋方法,在橋接方法再調(diào)用自己寫的方法實(shí)現(xiàn)。

泛型參數(shù)的繼承關(guān)系

在Java中,大家比較熟悉的是通過(guò)繼承機(jī)制而產(chǎn)生的類型體系結(jié)構(gòu)。比如String繼承自O(shè)bject。根據(jù)Liskov替換原則,子類是可以替換父類的。當(dāng)需要Object類的引用的時(shí)候,如果傳入一個(gè)String對(duì)象是沒有任何問(wèn)題的。但是反過(guò)來(lái)的話,即用父類的引用替換子類引用的時(shí)候,就需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。編譯器并不能保證運(yùn)行時(shí)刻這種轉(zhuǎn)換一定是合法的。這種自動(dòng)的子類替換父類的類型轉(zhuǎn)換機(jī)制,對(duì)于數(shù)組也是適用的。 String[]可以替換Object[]。但是泛型的引入,對(duì)于這個(gè)類型系統(tǒng)產(chǎn)生了一定的影響。正如前面提到的List是不能替換掉List的。

引入泛型之后的類型系統(tǒng)增加了兩個(gè)維度:一個(gè)是類型參數(shù)自身的繼承體系結(jié)構(gòu),另外一個(gè)是泛型類或接口自身的繼承體系結(jié)構(gòu)。第一個(gè)指的是對(duì)于 List和List這樣的情況,類型參數(shù)String是繼承自O(shè)bject的。而第二種指的是 List接口繼承自Collection接口。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則:

相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。即List可以賦給Collection 類型的引用,List可以替換Collection。這種情況也適用于帶有上下界的類型聲明。
當(dāng)泛型類的類型聲明中使用了通配符的時(shí)候, 這種替換的判斷可以在兩個(gè)維度上分別展開。如對(duì)Collection來(lái)說(shuō),用來(lái)替換他的引用可以在Collection這個(gè)維度上展開,即List和Set等;也可以在Number這個(gè)層次上展開,即Collection和 Collection等。如此循環(huán)下去,ArrayList和 HashSet等也都可以替換Collection。

如果泛型類中包含多個(gè)類型參數(shù),則對(duì)于每個(gè)類型參數(shù)分別應(yīng)用上面的規(guī)則。理解了上面的規(guī)則之后,就可以很容易的修正實(shí)例分析中給出的代碼了。只需要把List改成List即可。List可以替換List的子類型,因此傳遞參數(shù)時(shí)不會(huì)發(fā)生錯(cuò)誤。

個(gè)人認(rèn)為這里對(duì)上面這種情形使用子類型這種說(shuō)法來(lái)形容這種關(guān)系是不當(dāng)?shù)模驗(yàn)長(zhǎng)ist等本質(zhì)上來(lái)說(shuō)不能算作類型,只是對(duì)List類型加上了編譯器檢查約束,也就不存在子類型這種說(shuō)法。只能用是否在賦值時(shí)能夠進(jìn)行類型轉(zhuǎn)換來(lái)說(shuō)明。

泛型使用中的注意點(diǎn) 運(yùn)行時(shí)型別查詢
// 錯(cuò)誤,為類型擦除之后,ArrayList只剩下原始類型,泛型信息String不存在了,無(wú)法進(jìn)行判斷
if( arrayList instanceof ArrayList) 

if( arrayList instanceof ArrayList)    // 正確
異常中使用泛型的問(wèn)題

不能拋出也不能捕獲泛型類的對(duì)象。事實(shí)上,泛型類擴(kuò)展Throwable都不合法。為什么不能擴(kuò)展Throwable,因?yàn)楫惓6际窃谶\(yùn)行時(shí)捕獲和拋出的,而在編譯的時(shí)候,泛型信息全都會(huì)被擦除掉。類型信息被擦除后,那么多個(gè)使用不同泛型參數(shù)地方的catch都變?yōu)樵碱愋蚈bject,那么也就是說(shuō),多個(gè)地方的catch變的一模一樣,這自然不被允許。

不能再catch子句中使用泛型變量。

public static  void doWork(Class t){  
        try{  
            ...  
        }catch(T e){ //編譯錯(cuò)誤  T->Throwable,下面的永遠(yuǎn)不會(huì)被捕獲,所以不被允許
            ...  
        }catch(IndexOutOfBounds e){  
        }                           
 }  
不允許創(chuàng)建泛型類數(shù)組
Pair[] table = new Pair[10];// 編譯錯(cuò)誤
Pair[] table = new Pair[10];// 無(wú)編譯錯(cuò)誤

由于數(shù)組必須攜帶自己元素的類型信息,在類型擦除之后,Pair數(shù)組就變成了Pair數(shù)組,數(shù)組只能攜帶它的元素是Pair這樣的信息,但是并不能攜帶其泛型參數(shù)類型的信息,所以也就無(wú)法保證table[i]賦值的類型安全。編譯器只能禁用這種操作。

泛型類中的靜態(tài)方法和靜態(tài)變量

泛型類中的靜態(tài)方法和靜態(tài)變量不可以使用泛型類所聲明的泛型類型參數(shù)。

public class Test2 {    
    public static T one;   //編譯錯(cuò)誤    
    public static  T show(T one){ //編譯錯(cuò)誤    
        return null;    
    }    
}

因?yàn)榉盒皖愔械姆盒蛥?shù)的實(shí)例化是在定義對(duì)象的時(shí)候指定的,而靜態(tài)變量和靜態(tài)方法不需要使用對(duì)象來(lái)調(diào)用。對(duì)象都沒有創(chuàng)建,如何確定這個(gè)泛型參數(shù)是何種類型,所以當(dāng)然是錯(cuò)誤的。

類型擦除后的沖突
class Pair   {  
    public boolean equals(T value) {  
        return null;  
    }        
}  

方法重定義了,同時(shí)存在兩個(gè)equals(Object o)。

參考文章

Java深度歷險(xiǎn)(五)——Java泛型

Java語(yǔ)法糖(3):泛型

java泛型(二)、泛型的內(nèi)部原理:類型擦除以及類型擦除帶來(lái)的問(wèn)題

java泛型:通配符的使用

java泛型詳解

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/67208.html

相關(guān)文章

  • Java知識(shí)點(diǎn)匯總

    摘要:由于類型擦除機(jī)制的存在,泛型類中的類型參數(shù)等信息,在運(yùn)行時(shí)刻是不存在的。對(duì)此,對(duì)類文件的格式做了修訂,添加了屬性,用來(lái)包含不在類型系統(tǒng)中的類型信息。在運(yùn)行時(shí)刻,會(huì)讀取屬性的內(nèi)容并提供給反射來(lái)使用。 OOP 對(duì)象的創(chuàng)建和拷貝 對(duì)象的初始化 多態(tài)的實(shí)現(xiàn) 內(nèi)部類、匿名類、靜態(tài)類 對(duì)象內(nèi)存模型 上面內(nèi)容均請(qǐng)參考以下文章: 談?wù)凧ava的面向?qū)ο?運(yùn)行時(shí) 異常 聊聊Java的異常機(jī)制及實(shí)現(xiàn) ...

    Chao 評(píng)論0 收藏0
  • 是你眼中泛型嗎?

    摘要:得出的結(jié)論是兩個(gè)方法如果有相同的名稱和特征簽名,但返回值不同,那他們也是可以合法地共存于一個(gè)文件中的。同名方法參數(shù)個(gè)數(shù)順序類型不同,與返回值類型無(wú)關(guān)。這是來(lái)自中的一個(gè)例子。 繼《? extends T與? super T》之后,我們?cè)倭牧姆盒汀?Demo 1 public interface Generator { T next(); } 第一種解決方法,在方法返回類型前加...

    alphahans 評(píng)論0 收藏0
  • 淺析Java泛型

    摘要:泛型類在類的申明時(shí)指定參數(shù),即構(gòu)成了泛型類。換句話說(shuō),泛型類可以看成普通類的工廠。的作用就是指明泛型的具體類型,而類型的變量,可以用來(lái)創(chuàng)建泛型類的對(duì)象。只有聲明了的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用,也就是說(shuō)所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),...

    godiscoder 評(píng)論0 收藏0
  • 理解Java泛型(一)

    摘要:參數(shù)化的類型其中是參數(shù)化的類型。類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)其中是類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)。它們并沒有重載,而且泛型中也不存在重載這一說(shuō)法。除此之外,我們應(yīng)該盡量地多用泛型方法,而減少對(duì)整個(gè)類的泛化,因?yàn)榉盒头椒ǜ菀装咽虑檎f(shuō)明白。 泛型是適用于許多許多的類型 ---《JAVA編程思想》 在Java的面向?qū)ο缶幊踢^(guò)程中, 或許你知道運(yùn)用繼承、接口等一系列面向?qū)ο蟮膭?dòng)作來(lái)實(shí)現(xiàn)代碼復(fù)用...

    YFan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

lewif

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<