JAVA泛型

 
泛型類
  1. publicclassContainer<K, V>{
  2.     private K key;
  3.     private V value;
  4.  
  5.     publicContainer(K k, V v){
  6.         key = k;
  7.         value = v;
  8.     }
  9.  
  10.     public K getKey(){
  11.         return key;
  12.     }
  13.  
  14.     publicvoid setKey(K key){
  15.         this.key = key;
  16.     }
  17.  
  18.     public V getValue(){
  19.         return value;
  20.     }
  21.  
  22.     publicvoid setValue(V value){
  23.         this.value = value;
  24.     }
  25. }
從泛型類派生子類:
  1. publicclass A extends Apple<T>  //錯誤
  2. publicclass A extends Apple<String>//正確,把Apple<T>中的T看成String處理
  3. publicclass A extends Apple  //能夠,但泛型檢查有警告:未檢查或不安全的操做,Apple<T>中的T看成Object處理
 
泛型接口
泛型接口實現的兩種方式:
一、定義子類:在子類的定義上也聲明泛型類型:
  1. interface Info<T>{        // 在接口上定義泛型 
  2.     public T getVar();// 定義抽象方法,抽象方法的返回值就是泛型類型 
  3. } 
  4. classInfoImpl<T> implements Info<T>{   // 定義泛型接口的子類 
  5.     private T var ;             // 定義屬性 
  6.     publicInfoImpl(T var){     // 經過構造方法設置屬性內容 
  7.         this.setVar(var);   
  8.     } 
  9.     publicvoid setVar(T var){ 
  10.         this.var = var ; 
  11.     } 
  12.     public T getVar(){ 
  13.         returnthis.var ; 
  14.     } 
  15. }; 
  16. publicclassGenericsDemo24{ 
  17.     publicstaticvoid main(String arsg[]){ 
  18.         Info<String> i = null;        // 聲明接口對象 
  19.         i =newInfoImpl<String>("李興華");// 經過子類實例化對象 
  20.         System.out.println("內容:"+ i.getVar()); 
  21.     } 
  22. };  
二、若是如今實現接口的子類不想使用泛型聲明,則在實現接口的時候直接指定好其具體的操做類型便可:
  1. interface Info<T>{        // 在接口上定義泛型 
  2.     public T getVar();// 定義抽象方法,抽象方法的返回值就是泛型類型 
  3. } 
  4. classInfoImpl implements Info<String>{   // 定義泛型接口的子類 
  5.     privateString var ;                // 定義屬性 
  6.     publicInfoImpl(String var){        // 經過構造方法設置屬性內容 
  7.         this.setVar(var);   
  8.     } 
  9.     publicvoid setVar(String var){ 
  10.         this.var = var ; 
  11.     } 
  12.     publicString getVar(){ 
  13.         returnthis.var ; 
  14.     } 
  15. }; 
  16. publicclassGenericsDemo25{ 
  17.     publicstaticvoid main(String arsg[]){ 
  18.         Info i = null;      // 聲明接口對象 
  19.         i =newInfoImpl("李興華");   // 經過子類實例化對象 
  20.         System.out.println("內容:"+ i.getVar()); 
  21.     } 
  22. };  
 
泛型方法
[訪問權限]<泛型標識> 返回類型 函數名稱(參數類型)
  1. classDemo{ 
  2.     public<T> T fun(T t){            // 能夠接收任意類型的數據 
  3.         return t ;                  // 直接把參數返回 
  4.     } 
  5. }; 
  6. publicclassGenericsDemo26{ 
  7.     publicstaticvoid main(String args[]){ 
  8.         Demo d =newDemo();   // 實例化Demo對象 
  9.         String str = d.fun("李興華");//  傳遞字符串 
  10.         int i = d.fun(30);     // 傳遞數字,自動裝箱 
  11.         System.out.println(str);   // 輸出內容 
  12.         System.out.println(i);     // 輸出內容 
  13.     } 
  14. };  
 
泛型數組
  1. publicclassGenericsDemo30{ 
  2.     publicstaticvoid main(String args[]){ 
  3.         Integer i[]= fun1(1,2,3,4,5,6);   // 返回泛型數組 
  4.         fun2(i); 
  5.     } 
  6.     publicstatic<T> T[] fun1(T...arg){  // 接收可變參數 
  7.         return arg ;            // 返回泛型數組 
  8.     } 
  9.     publicstatic<T>void fun2(T param[]){   // 輸出 
  10.         System.out.print("接收泛型數組:"); 
  11.         for(T t:param){ 
  12.             System.out.print(t +"、"); 
  13.         } 
  14.     } 
  15. }; 
  16.  
 
類型擦除
使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。如:在代碼中定義的List<Object>和List<String>等類型,在編譯以後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來講是不可見的。 類型擦除也是Java的泛型實現方式與 C++模板機制 實現方式之間的重要區別。
類型擦除的基本過程也比較簡單,首先是找到用來替換類型參數的具體類:這個具體類通常是Object;若是指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。同時去掉出現的類型聲明,即去掉<>的內容。好比T.get()方法聲明就變成了Object.get();List<String>就變成了List。接下來就可能須要生成一些橋接方法(bridge method)。這是因爲擦除了類型以後的類可能缺乏某些必須的方法,這個時候就由編譯器來動態生成這個方法。
  • 泛型類並無本身獨有的Class類對象,無論泛型類型的實際類型參數是什麼,它們在運行時都有一樣的類(class)。好比並不存在List<String>.class或是List<Integer>.class,而只有List.class。
  • Class c1 =newArrayList<Integer>().getClass();
  • Class c2 =newArrayList<String>().getClass();
  • System.out.println(c1 == c2);
  •  
  • /* Output
  • true
  • */
  • 靜態變量是被泛型類的全部實例所共享的。對於聲明爲MyClass<T>的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不論是經過new MyClass<String>仍是new MyClass<Integer>建立的對象,都是共享一個靜態變量。因此在靜態方法、靜態變量的聲明和初始化中不容許使用類型形參:
  • instanceof運算符後不能使用泛型類
 
類型擦除緣由:
因爲泛型並非從Java誕生就存在的一個特性,而是等到SE5才被加入的,因此爲了兼容以前並未使用泛型的類庫和代碼,不得不讓編譯器擦除掉代碼中有關於泛型類型信息的部分,這樣最後生成出來的代碼實際上是『泛型無關』的,咱們使用別人的代碼或者類庫時也就不須要關心對方代碼是否已經『泛化』,反之亦然。
 
在泛型代碼內部,沒法得到任何有關泛型參數類型的信息。 泛型參數的類型對於JVM來講,實際上只是一堆佔位符。
  1. publicclassMain<T>{
  2.  
  3.     public T[] makeArray(){
  4.         // error: Type parameter 'T' cannot be instantiated directly
  5.         returnnew T[5];
  6.     }
  7. }
咱們沒法在泛型內部建立一個T類型的數組,緣由也和以前同樣,T僅僅是個佔位符,並無真實的類型信息,實際上,除了new表達式以外,instanceof操做和轉型(會收到警告)在泛型內部都是沒法使用的,而形成這個的緣由就是以前講過的編譯器對類型信息進行了擦除。
  同時,面對泛型內部形如T var;的代碼時,記得多念幾遍:它只是個Object,它只是個Object……
 
對泛型數組的擦除補償,本質方法仍是經過顯示地傳遞類型標籤,經過Array.newInstance(type, size)來生成數組,同時也是最爲推薦的在泛型內部生成數組的方法。 泛型內部生成數組的方法:
  1. publicclassMain<T>{
  2.  
  3.     public T[] create(Class<T> type){
  4.         return(T[])Array.newInstance(type,10);
  5.     }
  6.  
  7.     publicstaticvoid main(String[] args){
  8.         Main<String> m =newMain<String>();
  9.         String[] strings = m.create(String.class);
  10.     }
  11. }
 
雖然有類型擦除的存在,使得編譯器在泛型內部其實徹底沒法知道有關T的任何信息,可是 編譯器能夠保證重要的一點:內部一致性,也是咱們放進去的是什麼類型的對象,取出來仍是相同類型的對象,這一點讓Java的泛型起碼仍是有用武之地的。
編譯器承擔了所有的類型檢查工做:一個方法若是接收List<Object>做爲形式參數,那麼若是嘗試將一個List<String>的對象做爲實際參數傳進去,會沒法經過編譯( 拋出 ClassCastException )。
在java中,通常狀況下: 子類是能夠替換父類。好比在參數傳遞中, String[]能夠替換Object[],但 List<String>是不能替換掉List<Object>的。 Object是 String的父類,但 List<Object>不是 List<String>的父類
  1. publicvoid inspect(List<Object>list){   
  2.     for(Object obj :list){       
  3.         System.out.println(obj);   
  4.     }   
  5.     list.add(1);//這個操做在當前方法的上下文是合法的。
  6. }
  7. publicvoid test(){   
  8.     List<String> strs =newArrayList<String>();   
  9.     inspect(strs);//編譯錯誤
  10. }  
 
類型通配符<?>
通配符所表明的實際上是一組類型,但具體的類型是未知的。如List<?>就聲明瞭List中包含的元素類型是未知的。不能經過new ArrayList<?>()的方法來建立一個新的ArrayList對象或往 List<?>中添加元素 ,由於編譯器沒法知道具體的類型是什麼;可是對於 List<?>中的元素確老是能夠用Object來引用的,如調用List的get方法來返回指定索引處的元素,由於雖然類型未知,但確定是Object及其子類。
 
使用上下界來限制未知類型的範圍<? extends Shape>
List<? extends Number>說明List中可能包含的元素類型是Number及其子類
List<? super Number>說明List中包含的是Number及其父類。
當引入了上界以後,在使用類型的時候就可使用上界類中定義的方法。好比訪問 List<? extends Number>的時候,就可使用Number類的intValue等方法。一樣因爲 編譯器沒法知道具體的類型是什麼,故不能往其中添加元素。
使用:
 
  1. 通配符修飾的泛型不能用來直接建立變量對象
  2. 通配符修飾至關於聲明瞭一種變量,它能夠做爲參數在方法中傳遞。這麼作帶來的好處就是咱們能夠將應用於包含某些數據類型的列表的方法也應用到包含其子類型的列表中。至關於能夠在列表中用到一些面向對象的特性。
 
最佳實踐
  • 在代碼中避免泛型類和原始類型的混用。好比List<String>和List不該該共同使用。這樣會產生一些編譯器警告和潛在的運行時異常。當須要利用JDK 5以前開發的遺留代碼,而不得不這麼作時,也儘量的隔離相關的代碼。
  • 在使用帶通配符的泛型類的時候,須要明確通配符所表明的一組類型的概念。因爲具體的類型是未知的,不少操做是不容許的。
  • 泛型類最好不要同數組一塊使用。你只能建立new List<?>[10]這樣的數組,沒法建立new List<String>[10]這樣的。這限制了數組的使用能力,並且會帶來不少費解的問題。所以,當須要相似數組的功能時候,使用集合類便可。
  • 不要忽視編譯器給出的警告信息。
 
 
參考與閱讀:
http://www.javashuo.com/article/p-aanhuosv-eh.html(泛型類、泛型接口和泛型方法)
相關文章
相關標籤/搜索