深入理解java泛型



一、 什么是泛型?

泛 型(Generic type 或者 generics)是對 簡單的理解,就是對類型的參數化,比如我們定義一個類屬性或者實例屬性時,往往要指定具體的類型,如Integer、Person等等, 但是如果使用了泛型,我們把這些具體的類型參數化,用一個廣泛的可以表示所有類型的“類型”T來定義,那這個T就是泛型的表示。

可以在集合框架(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map 添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String)的對象。

因為 Map.get() 被定義為返回 Object,所以一般必須將 Map.get() 的結果強制類型轉換為期望的類型,如下面的代碼所示:

  1. Map m = new HashMap();  
  2. m.put("key", "value");  
  3. String s = (String) m.get("key");  
要讓程序通過編譯,必須將 get() 的結果強制類型轉換為 String,並且希望結果真的是一個 String。如果map中保存了的不是 String 的數據,則上面的代碼將會拋出 ClassCastException。


二、 泛型的好處

Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:
1、 類型安全。 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。
2、 消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。

3、 潛在的性能收益。 泛型為較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的字節碼中。


三、 泛型用法的例子

 3.1  我們再程序中定義一個類,並制定泛型參數

 

  1. class Point<T>{       // 此處可以隨便寫標識符號,T是type的簡稱  
  2.     private T var ; // var的類型由T指定,即:由外部指定  
  3.     public T getVar(){  // 返回值的類型由外部決定  
  4.         return var ;  
  5.     }  
  6.     public void setVar(T var){  // 設置的類型也由外部決定  
  7.         this.var = var ;  
  8.     }  
  9. };  
  10. public class GenericsDemo{  
  11.     public static void main(String args[]){  
  12.         Point<String> p = new Point<String>() ; // 里面的var類型為String類型  
  13.         p.setVar("MLDN") ;      // 設置字符串  
  14.         System.out.println(p.getVar().length()) ;   // 取得字符串的長度  
  15.     }  
  16. };  

說明: 

1. 命名類型參數
推薦的命名約定是使用大寫的單個字母名稱作為類型參數。這與 C++ 約定有所不同(參閱 附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量類型參數的假定。對於常見的泛型模式,推薦的名稱是:
K —— 鍵,比如映射的鍵。 
V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。 
E —— 異常類。 
T —— 泛型。

2. 以上,是將var變量設置為了String類型,當然也可以設置為其他的數據類型,比如Integer等,如果你設置的內容與你制定的泛型類型不一致,則在編譯時將出現錯誤。比如:

 

  1. class Point<T>{       // 此處可以隨便寫標識符號,T是type的簡稱  
  2.     private T var ; // var的類型由T指定,即:由外部指定  
  3.     public T getVar(){  // 返回值的類型由外部決定  
  4.         return var ;  
  5.     }  
  6.     public void setVar(T var){  // 設置的類型也由外部決定  
  7.         this.var = var ;  
  8.     }  
  9. };  
  10. public class GenericsDemo{  
  11.     public static void main(String args[]){  
  12.         Point<Integer> p = new Point<Integer>() ;   // 里面的var類型為String類型  
  13.         p.setVar("MLDN") ;      // 設置字符串  
  14.     }  
  15. };  
程序在編譯期就出報錯:

 

  1. GenericsDemo.java:13: 錯誤: 無法將類 Point<T>中的方法 setVar應用到給定類型;  
  2.         p.setVar("MLDN") ;      // 設置字符串  
  3.          ^  
  4.   需要: Integer  
  5.   找到: String  
  6.   原因: 無法通過方法調用轉換將實際參數String轉換為Integer  
  7.   其中, T是類型變量:  
  8.     T擴展已在類 Point中聲明的Object  
  9. 1 個錯誤  


看log就可以發現,我我們已經規定了泛型的類型為Integer,則說明T類型就是Integer,所以在傳入參數時,當然不能傳入String類型的參數了。


 3.2  構造方法中使用泛型

構造方法可以為類中的屬性進行初始化,如果類中的屬性用過泛型指定,而又需要通過構造器設置屬性的內容時,那么構造方法的定義與之前並無不同,不需要像聲明類那樣指定泛型。

  1. class Point<T>{       // 此處可以隨便寫標識符號,T是type的簡稱  
  2.     private T var ; // var的類型由T指定,即:由外部指定  
  3.     public Point(T var){        // 通過構造方法設置內容  
  4.         this.var = var ;  
  5.     }  
  6.     public T getVar(){  // 返回值的類型由外部決定  
  7.         return var ;  
  8.     }  
  9.     public void setVar(T var){  // 設置的類型也由外部決定  
  10.         this.var = var ;  
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Point<String> p = new Point<String>("MLDN") ;   // 里面的var類型為String類型  
  16.         System.out.println("內容:" + p.getVar()) ;  
  17.     }  
  18. };  

這里我們講一個泛型的警告問題: 當你為某個類只定了泛型,但是,你實例化該類的對象的時候,並沒有指定泛型的類型,則程序在編譯時會出現警告,警告並不會影響程序的運行。 

 

  1. class Info<T>{  
  2.     private T var ;  
  3.     public T getVar(){  
  4.         return this.var ;  
  5.     }  
  6.     public void setVar(T var){  
  7.         this.var = var ;  
  8.     }  
  9.     public String toString(){       // 覆寫Object類中的toString()方法  
  10.         return this.var.toString() ;  
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info i = new Info() ;       // 警告,沒有指定泛型類型  
  16.         i.setVar("MLDN") ;          // 設置字符串  
  17.         System.out.println("內容:" + i.getVar()) ;  
  18.     }  
  19. };  
編譯程序會出現警告:
注: GenericsDemo10.java使用了未經檢查或不安全的操作。
注: 有關詳細信息, 請使用 -Xlint:unchecked 重新編譯。

說明: 由於沒有指定泛型類型,則類可以接受任何數據類型,也就是此時的var的類型就是Object,所有的泛型信息都會被擦除。


三、 泛型通配符

3.1 引入泛型通配符

我們先來看一個例子:

 

  1. class Info<T>{  
  2.     private T var ;     // 定義泛型變量  
  3.     public void setVar(T var){  
  4.         this.var = var ;  
  5.     }  
  6.     public T getVar(){  
  7.         return this.var ;  
  8.     }  
  9.     public String toString(){   // 直接打印  
  10.         return this.var.toString() ;  
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info<String> i = new Info<String>() ;       // 使用String為泛型類型  
  16.         i.setVar("MLDN") ;                          // 設置內容  
  17.         fun(i) ;  
  18.     }  
  19.     public static void fun(Info<Object> temp){        // 接收Object泛型類型的Info對象  
  20.         System.out.println("內容:" + temp) ;  
  21.     }  
  22. };  
在方法調用過程中,我們將Info<String>傳遞給Info<Object>,此時會發現,程序在編譯時會報錯:

 

  1. GenericsDemo.java:17: 錯誤: 無法將類 GenericsDemo中的方法 fun應用到給定類型;  
  2.         fun(i) ;  
  3.         ^  
  4.   需要: Info<Object>  
  5.   找到: Info<String>  
  6.   原因: 無法通過方法調用轉換將實際參數Info<String>轉換為Info<Object>  
  7. 1 個錯誤</span>  


上述錯誤說明,泛型對象進行引用傳遞的時候,類型必須一致,Info<Object>並不是Info<String>的父類。如果現在非要傳遞,則可以講fun方法中的info參數的泛型取消掉:

 

  1. public static void main(String args[]){  
  2.     Info<String> i = new Info<String>() ;       // 使用String為泛型類型  
  3.     i.setVar("MLDN") ;                          // 設置內容  
  4.     fun(i) ;  
  5. }  
  6. public static void fun(Info temp){      // 接收Object泛型類型的Info對象  
  7.     System.out.println("內容:" + temp) ;  
  8. }  

當然,這樣看來程序已經可以正常運行了,但是,我們之前已經指定了泛型,此時卻在方法傳遞過程中把它取消了,總是不妥的,所以,java提供了?通配符來匹配任何的泛型類型。

 

  1. public class GenericsDemo{  
  2.     public static void main(String args[]){  
  3.         Info<String> i = new Info<String>() ;       // 使用String為泛型類型  
  4.         i.setVar("MLDN") ;                          // 設置內容  
  5.         fun(i) ;  
  6.     }  
  7.     public static void fun(Info<?> temp){     // 可以接收任意的泛型對象  
  8.         System.out.println("內容:" + temp) ;  
  9.     }  
  10. };  
我們應當注意,在fun方法中,我們是直接輸出了temp對象,並為其做任何修改,實質上,使用?可以接收任意的內容,但是此內容卻無法直接使用<?>進行修改,比如:我們這樣去創建一個對象:

 

  1. public class GenericsDemo{  
  2.     public static void main(String args[]){  
  3.         Info<?> i = new Info<String>() ;        // 使用String為泛型類型  
  4.         i.setVar("MLDN") ;                          // 設置內容  
  5.     }  
  6. };  
編譯后,程序會報錯:

 

  1. GenericsDemo.java:16: 錯誤: 無法將類 Info<T>中的方法 setVar應用到給定類型;  
  2.         i.setVar("MLDN") ;                          // 設置內容  
  3.          ^  
  4.   需要: CAP#1  
  5.   找到: String  
  6.   原因: 無法通過方法調用轉換將實際參數String轉換為CAP#1  
  7.   其中, T是類型變量:  
  8.     T擴展已在類 Info中聲明的Object  
  9.   其中, CAP#1是新類型變量:  
  10.     CAP#1從?的捕獲擴展Object  
  11. 1 個錯誤  


四、 受限泛型

4.1 泛型上限: 表示參數化的類型可能是所指定類型,或者是其子類。

 

  1. class Info<T>{  
  2.     private T var ;     // 定義泛型變量  
  3.     public void setVar(T var){  
  4.         this.var = var ;  
  5.     }  
  6.     public T getVar(){  
  7.         return this.var ;  
  8.     }  
  9.     public String toString(){   // 直接打印  
  10.         return this.var.toString() ;  
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info<Integer> i1 = new Info<Integer>() ; // 聲明Integer的泛型對象  
  16.         Info<Float> i2 = new Info<Float>() ;  // 聲明Float的泛型對象  
  17.         i1.setVar(30) ;  // 設置整數,自動裝箱  
  18.         i2.setVar(30.1f) ;  // 設置小數,自動裝箱  
  19.         fun(i1) ;  
  20.         fun(i2) ;  
  21.     }  
  22.     public static void fun(Info<? extends Number> temp){  // 只能接收Number及其Number的子類  
  23.         System.out.print(temp + "、") ;  
  24.     }  
  25. };  


如果你接收的不是Number類及其子類,則程序會報錯:

 

  1. public class GenericsDemo{  
  2.     public static void main(String args[]){  
  3.         Info<String> i1 = new Info<String>() ;      // 聲明String的泛型對象  
  4.         i1.setVar("hello") ;  
  5.         fun(i1) ;  
  6.     }  
  7.     public static void fun(Info<? extends Number> temp){  // 只能接收Number及其Number的子類  
  8.         System.out.print(temp + "、") ;  
  9.     }  
  10. };  
錯誤: 無法將類 GenericsDemo中的方法 fun應用到給定類型;
fun(i1) ;
^
  需要: Info<? extends Number>
  找到: Info<String>
  原因: 無法通過方法調用轉換將實際參數Info<String>轉換為Info<? extends Number>
1 個錯誤


4.2 泛型下限:使用的泛型只能是本類及其父類類型上應用的時候,就必須使用泛型的下限。
  1. class Info<T>{  
  2.     private T var ;     // 定義泛型變量  
  3.     public void setVar(T var){  
  4.         this.var = var ;  
  5.     }  
  6.     public T getVar(){  
  7.         return this.var ;  
  8.     }  
  9.     public String toString(){   // 直接打印  
  10.         return this.var.toString() ;  
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info<String> i1 = new Info<String>() ;      // 聲明String的泛型對象  
  16.         Info<Object> i2 = new Info<Object>() ;      // 聲明Object的泛型對象  
  17.         i1.setVar("hello") ;  
  18.         i2.setVar(new Object()) ;  
  19.         fun(i1) ;  
  20.         fun(i2) ;  
  21.     }  
  22.     public static void fun(Info<? super String> temp){    // 只能接收String或Object類型的泛型  
  23.         System.out.print(temp + "、") ;  
  24.     }  
  25. };  


五、 泛型與子類繼承

一個類的子類可以通過對象多態性,為其父類實例化,但是在泛型操作中,子類的泛型類型是無法使用父類的泛型類型接受的,例如,Info<String>不能使用 Info<Object>接收。


六、 泛型接口

6.1 定義泛型接口

 

  1. interface Info<T>{   // 在接口上定義泛型  
  2.    public T getVar() ;  // 定義抽象方法,抽象方法的返回值就是泛型類型  
  3. }  


6.2 泛型接口的兩種實現方式

 6.2.1 定義子類,在子類的上也使用泛型聲明

 

  1. interface Info<T>{        // 在接口上定義泛型  
  2.     public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型  
  3. }  
  4. class InfoImpl<T> implements Info<T>{   // 定義泛型接口的子類  
  5.     private T var ;             // 定義屬性  
  6.     public InfoImpl(T var){     // 通過構造方法設置屬性內容  
  7.         this.setVar(var) ;    
  8.     }  
  9.     public void setVar(T var){  
  10.         this.var = var ;  
  11.     }  
  12.     public T getVar(){  
  13.         return this.var ;  
  14.     }  
  15. };  
  16. public class GenericsDemo{  
  17.     public static void main(String arsg[]){  
  18.         Info<String> i = null;        // 聲明接口對象  
  19.         i = new InfoImpl<String>("李興華") ; // 通過子類實例化對象  
  20.         System.out.println("內容:" + i.getVar()) ;  
  21.     }  
  22. };  

6.2.2 定義子類,直接指定泛型的具體操作類型

 

  1. interface Info<T>{        // 在接口上定義泛型  
  2.     public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型  
  3. }  
  4. class InfoImpl implements Info<String>{   // 定義泛型接口的子類  
  5.     private String var ;                // 定義屬性  
  6.     public InfoImpl(String var){        // 通過構造方法設置屬性內容  
  7.         this.setVar(var) ;    
  8.     }  
  9.     public void setVar(String var){  
  10.         this.var = var ;  
  11.     }  
  12.     public String getVar(){  
  13.         return this.var ;  
  14.     }  
  15. };  
  16. public class GenericsDemo{  
  17.     public static void main(String arsg[]){  
  18.         Info i = null;      // 聲明接口對象  
  19.         i = new InfoImpl("李興華") ;   // 通過子類實例化對象  
  20.         System.out.println("內容:" + i.getVar()) ;  
  21.     }  
  22. };  


七、 泛型方法


7.1 泛型方法中可以定義泛型參數,此時,參數的類型就是傳入數據的類型。

  1. class Demo{  
  2.     public <T> T fun(T t){            // 可以接收任意類型的數據  
  3.         return t ;          // 直接把參數返回  
  4.     }  
  5. };  
  6. public class GenericsDemo{  
  7.     public static void main(String args[]){  
  8.         Demo d = new Demo() ;   // 實例化Demo對象  
  9.         String str = d.fun("李興華") ;  // 傳遞字符串  
  10.         int i = d.fun(30) ;     // 傳遞數字,自動裝箱  
  11.         System.out.println(str) ;   // 輸出內容  
  12.         System.out.println(i) ;     // 輸出內容  
  13.     }  
  14. };  

7.2 通過泛型方法,返回泛型類的實例
  1. class Info<T extends Number>{ // 指定上限,只能是數字類型  
  2.     private T var ;     // 此類型由外部決定  
  3.     public T getVar(){  
  4.         return this.var ;     
  5.     }  
  6.     public void setVar(T var){  
  7.         this.var = var ;  
  8.     }  
  9.     public String toString(){       // 覆寫Object類中的toString()方法  
  10.         return this.var.toString() ;      
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info<Integer> i = fun(30) ;  
  16.         System.out.println(i.getVar()) ;  
  17.     }  
  18.     public static <T extends Number> Info<T> fun(T param){  
  19.         Info<T> temp = new Info<T>() ;      // 根據傳入的數據類型實例化Info  
  20.         temp.setVar(param) ;        // 將傳遞的內容設置到Info對象的var屬性之中  
  21.         return temp ;   // 返回實例化對象  
  22.     }  
  23. };  

7.2 使用泛型,統一傳遞參數的類型

 

  1. class Info<T>{    // 指定上限,只能是數字類型  
  2.     private T var ;     // 此類型由外部決定  
  3.     public T getVar(){  
  4.         return this.var ;     
  5.     }  
  6.     public void setVar(T var){  
  7.         this.var = var ;  
  8.     }  
  9.     public String toString(){       // 覆寫Object類中的toString()方法  
  10.         return this.var.toString() ;      
  11.     }  
  12. };  
  13. public class GenericsDemo{  
  14.     public static void main(String args[]){  
  15.         Info<String> i1 = new Info<String>() ;  
  16.         Info<String> i2 = new Info<String>() ;  
  17.         i1.setVar("HELLO") ;        // 設置內容  
  18.         i2.setVar("李興華") ;      // 設置內容  
  19.         add(i1,i2) ;  
  20.     }  
  21.     public static <T> void add(Info<T> i1,Info<T> i2){  
  22.         System.out.println(i1.getVar() + " " + i2.getVar()) ;  
  23.     }  
  24. };  


對於上述程序,我們再add方法里指定的T類型必須一致,比如上面指定了兩個String類型,如果你傳遞的不一致,則會出現錯誤。

 

  1. public class GenericsDemo{  
  2.     public static void main(String args[]){  
  3.         Info<Integer> i1 = new Info<Integer>() ;  
  4.         Info<String> i2 = new Info<String>() ;  
  5.         i1.setVar(30) ;     // 設置內容  
  6.         i2.setVar("李興華") ;      // 設置內容  
  7.         add(i1,i2) ;  
  8.     }  
  9.     public static <T> void add(Info<T> i1,Info<T> i2){  
  10.         System.out.println(i1.getVar() + " " + i2.getVar()) ;  
  11.     }  
  12. };  

編譯時報錯:

 

  1. GenericsDemo.java:19: 錯誤: 無法將類 GenericsDemo中的方法 add應用到給定類型;  
  2.         add(i1,i2) ;  
  3.         ^  
  4.   需要: Info<T>,Info<T>  
  5.   找到: Info<Integer>,Info<String>  
  6.   原因: 不存在類型變量T的實例, 以使參數類型Info<String>與形式參數類型Info<T>一致  
  7.   其中, T是類型變量:  
  8.     T擴展已在方法 <T>add(Info<T>,Info<T>)中聲明的Object  
  9. 1 個錯誤  

六、 泛型數組

不能創建一個確切泛型類型的數組。如下面代碼會出錯。
List<String>[] lsa = new ArrayList<String>[10]; 
因為如果可以這樣,那么考慮如下代碼,會導致運行時錯誤。 

  1. List<String>[] lsa = new ArrayList<String>[10]; // 實際上並不允許這樣創建數組    
  2. Object o = lsa;    
  3. Object[] oa = (Object[]) o;    
  4. List<Integer>li = new ArrayList<Integer>();    
  5. li.add(new Integer(3));    
  6. oa[1] = li;// unsound, but passes run time store check    
  7. String s = lsa[1].get(0); //run-time error - ClassCastException  


因此只能創建帶通配符的泛型數組,如下面例子所示,這回可以通過編譯,但是在倒數第二行代碼 中必須顯式的轉型才行,即便如此,最后還是會拋出類型轉換異常,因為存儲在lsa中的是List<Integer>類型的對象,而不是 List<String>類型。最后一行代碼是正確的,類型匹配,不會拋出異常。
  1. List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type    
  2. Object o = lsa;    
  3. Object[] oa = (Object[]) o;    
  4. List<Integer>li = new ArrayList<Integer>();    
  5. li.add(new Integer(3));    
  6. oa[1] = li; //correct    
  7. String s = (String) lsa[1].get(0);// run time error, but cast is explicit    
  8. Integer it = (Integer)lsa[1].get(0); // OK    
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM