日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
聊聊Java的泛型及實現(xiàn)

泛型基礎(chǔ)

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

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計、成都網(wǎng)站制作、灞橋網(wǎng)絡(luò)推廣、小程序定制開發(fā)、灞橋網(wǎng)絡(luò)營銷、灞橋企業(yè)策劃、灞橋品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供灞橋建站搭建服務(wù),24小時服務(wù)熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com

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

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

  3. 為較大的優(yōu)化帶來了可能

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

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

  • 定義一個泛型類***到底會生成幾個類,比如ArrayList到底有幾個類
  • 定義一個泛型方法最終會有幾個方法在class文件中
  • 為什么泛型參數(shù)不能是基本類型呢
  • ArrayList是一個類嗎
  • ArrayList和List和ArrayList和List是什么關(guān)系呢,這幾個類型的引用能相互賦值嗎

類型擦除

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

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

  • 泛型類并沒有自己獨有的Class類對象。比如并不存在List.class或是List.class,而只有List.class。
  • 靜態(tài)變量是被泛型類的所有實例所共享的。對于聲明為MyClass的類,訪問其中的靜態(tài)變量的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass還是new MyClass創(chuàng)建的對象,都是共享一個靜態(tài)變量。
  • 泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進(jìn)行的。由于類型信息被擦除,JVM是無法區(qū)分兩個異常類型MyException和MyException的。對于JVM來說,它們都是 MyException類型的。也就無法執(zhí)行與異常對應(yīng)的catch語句。

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

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

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

保證類型安全

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

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

這樣是沒有錯誤的,不過會有個編譯時警告。不過在***種情況,可以實現(xiàn)與 完全使用泛型參數(shù)一樣的效果,第二種則完全沒效果。因為,本來類型檢查就是編譯時完成的。new ArrayList()只是在內(nèi)存中開辟一個存儲空間,可以存儲任何的類型對象。而真正涉及類型檢查的是它的引用,因為我們是使用它引用arrayList1 來調(diào)用它的方法,比如說調(diào)用add()方法。所以arrayList1引用能完成泛型類型的檢查。 而引用arrayList2沒有使用泛型,所以不行。

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

實現(xiàn)自動類型轉(zhuǎn)換

因為類型擦除的問題,所以所有的泛型類型變量***都會被替換為原始類型。這樣就引起了一個問題,既然都被替換為原始類型,那么為什么我們在獲取的時候,不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換呢?

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

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

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

先看一個例子:

  
 
 
 
  1. class DateInter extends A {   
  2.     @Override   
  3.     public void setValue(Date value) {   
  4.         super.setValue(value);   
  5.     }   
  6.     @Override   
  7.     public Date getValue() {   
  8.         return super.getValue();   
  9.     }   

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

  
 
 
 
  1. public void setValue(java.util.Date);  //我們重寫的setValue方法   
  2.     Code:   
  3.        0: aload_0   
  4.        1: aload_1   
  5.        2: invokespecial #16                // invoke A setValue 
  6. :(Ljava/lang/Object;)V   
  7.        5: return   
  8.  
  9.   public java.util.Date getValue();    //我們重寫的getValue方法   
  10.     Code:   
  11.        0: aload_0   
  12.        1: invokespecial #23                 // A.getValue   
  13. :()Ljava/lang/Object;   
  14.        4: checkcast     #26                
  15.        7: areturn   
  16.  
  17.   public java.lang.Object getValue();     //編譯時由編譯器生成的方法   
  18.     Code:   
  19.        0: aload_0   
  20.        1: invokevirtual #28                 // Method getValue:() 去調(diào)用我們重寫的getValue方法   
  21. ;   
  22.        4: areturn   
  23.  
  24.   public void setValue(java.lang.Object);   //編譯時由編譯器生成的方法   
  25.     Code:   
  26.        0: aload_0   
  27.        1: aload_1   
  28.        2: checkcast     #26                  
  29.        5: invokevirtual #30                 // Method setValue;   去調(diào)用我們重寫的setValue方法   
  30. )V   
  31.        8: return 

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

我們再看一個經(jīng)常出現(xiàn)的例子。

  
 
 
 
  1. class A { 
  2.     Object get(){ 
  3.         return new Object(); 
  4.     } 
  5.  
  6. class B extends A { 
  7.     @Override 
  8.     Integer get() { 
  9.         return new Integer(1); 
  10.     } 
  11.  
  12.   public static void main(String[] args){ 
  13.     A a = new B(); 
  14.     B b = (B) a; 
  15.     A c = new A(); 
  16.     a.get(); 
  17.     b.get(); 
  18.     c.get(); 
  19.   } 

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

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

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

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

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

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

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

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

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

泛型使用中的注意點

運行時型別查詢

  
 
 
 
  1. // 錯誤,為類型擦除之后,ArrayList只剩下原始類型,泛型信息String不存在了,無法進(jìn)行判斷 
  2. if( arrayList instanceof ArrayList)  
  3.  
  4. if( arrayList instanceof ArrayList)    // 正確 

異常中使用泛型的問題

  • 不能拋出也不能捕獲泛型類的對象。事實上,泛型類擴(kuò)展Throwable都不合法。為什么不能擴(kuò)展Throwable,因為異常都是在運行時捕獲和拋出的,而在編譯的時候,泛型信息全都會被擦除掉。類型信息被擦除后,那么多個使用不同泛型參數(shù)地方的catch都變?yōu)樵碱愋蚈bject,那么也就是說,多個地方的catch變的一模一樣,這自然不被允許。
  • 不能再catch子句中使用泛型變量。
  
 
 
 
  1. public static  void doWork(Class t){   
  2.         try{   
  3.             ...   
  4.         }catch(T e){ //編譯錯誤  T->Throwable,下面的永遠(yuǎn)不會被捕獲,所以不被允許 
  5.             ...   
  6.         }catch(IndexOutOfBounds e){   
  7.         }                            
  8.  } 

不允許創(chuàng)建泛型類數(shù)組

  
 
 
 
  1. Pair[] table = new Pair[10];// 編譯錯誤 
  2. Pair[] table = new Pair[10];// 無編譯錯誤 

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

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

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

  
 
 
 
  1. public class Test2 {     
  2.     public static T one;   //編譯錯誤     
  3.     public static  T show(T one){ //編譯錯誤     
  4.         return null;     
  5.     }     

因為泛型類中的泛型參數(shù)的實例化是在定義對象的時候指定的,而靜態(tài)變量和靜態(tài)方法不需要使用對象來調(diào)用。對象都沒有創(chuàng)建,如何確定這個泛型參數(shù)是何種類型,所以當(dāng)然是錯誤的。

類型擦除后的沖突

  
 
 
 
  1. class Pair   {   
  2.     public boolean equals(T value) {   
  3.         return null;   
  4.     }         

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

參考文章

  • Java深度歷險(五)——Java泛型
  • Java語法糖(3):泛型
  • java泛型(二)、泛型的內(nèi)部原理:類型擦除以及類型擦除帶來的問題
  • java泛型:通配符的使用

分享名稱:聊聊Java的泛型及實現(xiàn)
文章地址:http://www.5511xx.com/article/dpidpci.html