天天看點

11-java泛型總結

java泛型總結

   1.1 什麼是泛型

11-java泛型總結

      1.1.1 泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的資料類型被指定為一個參數。

      1.1.2 這種參數類型可以用在類、接口和方法的建立中,分别稱為泛型類、泛型接口、泛型方法。

   1.2 不使用泛型和使用泛型的對比

      1.2.1 在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實作參數的“任意化”,

            “任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。

        對于強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運作的時候才出現異常,這是一個安全隐患。

      1.2.2 泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隐式的,提高代碼的重用率。

   1.3 泛型的規則和限制

      1.3.1 泛型的類型參數隻能是類類型(包括自定義類),不能是基本資料類型。

      1.3.2 同一種泛型可以對應多個版本(因為參數類型是不确定的),不同版本的泛型類執行個體是不相容的。

      1.3.3 泛型的類型參數可以有多個。

      1.3.4 泛型的參數類型可以使用extends語句,例如<T extends superclass>。習慣上稱為“有界類型”。

      1.3.5 泛型的參數類型還可以是通配符類型。例如Class<?> classType = Class.forName("java.lang.String")。

      1.3.6 參數化類型可以引用一個原始類型的對象。

      1.3.7 原始類型也可以引用一個參數化類型的對象。

   1.4 ArrayList<E> 類定義和ArrayList<Integer>類中涉及術語:

      1.4.1 整個ArrayList<E>稱為泛型類型。

      1.4.2 ArrayList<E>中的E稱為類型變量或類型參數。

      1.4.3 整個ArrayList<Integer>稱為參數化的類型。

      1.4.4 ArrayList<Integer>中的Integer稱為類型參數的執行個體或實際類型參數

      1.4.5 ArrayList<Integer>中的<>念着typeof

      1.4.6 ArrayList稱為原始類型。

   1.5 泛型的使用原理:

      泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程式中的非法輸入,

      編譯器編譯帶類型說明的集合時會去除掉“類型”資訊,使程式運作效率不受影響。

      對于參數化的泛型類型,getClass()方法的傳回值和原始類型完全一樣。

      由于編譯生成的位元組碼會去掉泛型的類型資訊,隻要能跳過編譯器,

      就可以往某個泛型集合中加入其它類型的資料,例如,用反射得到集合對象,再調用其add方法即可。

   使用泛型和沒有使用泛型的對比

   使用泛型的例子:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class GenDemo {  
  2.     public static void main(String[] args) {  
  3.         // 定義泛型類Gen的一個Integer版本  
  4.         Gen<Integer> intOb = new Gen<Integer>(88);  
  5.         intOb.showType();  
  6.         int i = intOb.getOb();  
  7.         System.out.println("value= " + i);  
  8.         System.out.println("----------------------------------");  
  9.         // 定義泛型類Gen的一個String版本  
  10.         Gen<String> strOb = new Gen<String>("Hello Gen!");  
  11.         strOb.showType();  
  12.         String s = strOb.getOb();  
  13.         System.out.println("value= " + s);  
  14.     }  
  15. }  
  16. class Gen<T> {  
  17.     private T ob; // 定義泛型成員變量  
  18.     public Gen(T ob) {  
  19.         this.ob = ob;  
  20.     }  
  21.     public T getOb() {  
  22.         return ob;  
  23.     }  
  24.     public void setOb(T ob) {  
  25.         this.ob = ob;  
  26.     }  
  27.     public void showType() {  
  28.         System.out.println("T的實際類型是: " + ob.getClass().getName());  
  29.     }  
  30. }  

   沒有使用泛型的例子

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class GenDemo2 {  
  2.     public static void main(String[] args) {  
  3.         // 定義類Gen2的一個Integer版本  
  4.         Gen2 intOb = new Gen2(new Integer(88));  
  5.         intOb.showTyep();  
  6.         int i = (Integer) intOb.getOb();  
  7.         System.out.println("value= " + i);  
  8.         System.out.println("---------------------------------");  
  9.         // 定義類Gen2的一個String版本  
  10.         Gen2 strOb = new Gen2("Hello Gen!");  
  11.         strOb.showTyep();  
  12.         String s = (String) strOb.getOb();  
  13.         System.out.println("value= " + s);  
  14.     }  
  15. }  
  16. class Gen2 {  
  17.     private Object ob; // 定義一個通用類型成員  
  18.     public Gen2(Object ob) {  
  19.         this.ob = ob;  
  20.     }  
  21.     public Object getOb() {  
  22.         return ob;  
  23.     }  
  24.     public void setOb(Object ob) {  
  25.         this.ob = ob;  
  26.     }  
  27.     public void showTyep() {  
  28.         System.out.println("T的實際類型是: " + ob.getClass().getName());  
  29.     }  
  30. }  

    運作結果:

    兩個例子運作Demo結果是相同的,控制台輸出結果如下:

    T的實際類型是:

    java.lang.Integer

    value= 88

    ----------------------------------

    T的實際類型是: java.lang.String

    value= Hello Gen!

    Process finished with exit code 0

   java1.5之前類似泛型功能的示例和1.5之後使用泛型替代之前的寫法。

   原始代碼

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class StringFoo {  
  2.     private String x;  
  3.     public StringFoo(String x) {  
  4.         this.x = x;  
  5.     }  
  6.     public String getX() {  
  7.         return x;  
  8.     }  
  9.     public void setX(String x) {  
  10.         this.x = x;  
  11.     }  
  12. }  
  13. public class DoubleFoo {  
  14.     private Double x;  
  15.     public DoubleFoo(Double x) {  
  16.         this.x = x;  
  17.     }  
  18.     public Double getX() {  
  19.         return x;  
  20.     }  
  21.     public void setX(Double x) {  
  22.         this.x = x;  
  23.     }  
  24. }  

       重構

    因為上面的類中,成員和方法的邏輯都一樣,就是類型不一樣,是以考慮重構。Object是所有類的父類,

    是以可以考慮用Object做為成員類型,這樣就可以實作通用了,實際上就是“Object泛型”,暫時這麼稱呼。

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class ObjectFoo {  
  2.     private Object x;  
  3.     public ObjectFoo(Object x) {  
  4.         this.x = x;  
  5.     }  
  6.     public Object getX() {  
  7.         return x;  
  8.     }  
  9.     public void setX(Object x) {  
  10.         this.x = x;  
  11.     }  
  12. }  

    寫出Demo方法如下:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class ObjectFooDemo {  
  2.     public static void main(String args[]) {  
  3.         ObjectFoo strFoo = new ObjectFoo(new StringFoo("Hello Generics!"));  
  4.         ObjectFoo douFoo = new ObjectFoo(new DoubleFoo(33));  
  5.         ObjectFoo objFoo = new ObjectFoo(new Object());  
  6.         System.out.println("strFoo.getX=" + (StringFoo) strFoo.getX());  
  7.         System.out.println("douFoo.getX=" + (DoubleFoo) douFoo.getX());  
  8.         System.out.println("objFoo.getX=" + objFoo.getX());  
  9.     }  
  10. }  

    運作結果如下:

    strFoo.getX=Hello Generics!

    douFoo.getX=33.0

    [email protected]

    解說:在Java 5之前,為了讓類有通用性,往往将參數類型、傳回類型設定為Object類型,當擷取這些傳回類型來使用時候,

    必須将其“強制”轉換為原有的類型或者接口,然後才可以調用對象上的方法。

   泛型來實作

    強制類型轉換很麻煩,我還要事先知道各個Object具體類型是什麼,才能做出正确轉換。

    否則,要是轉換的類型不對,比如将“Hello Generics!”字元串強制轉換為Double,那麼編譯的時候不會報錯,

    可是運作的時候就不行了,改用 Java5泛型來實作。

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class GenericsFoo<T> {  
  2.     private T x;  
  3.     public GenericsFoo(T x) {  
  4.         this.x = x;  
  5.     }  
  6.     public T getX() {  
  7.         return x;  
  8.     }  
  9.     public void setX(T x) {  
  10.         this.x = x;  
  11.     }  
  12. }  
  13. public class GenericsFooDemo {  
  14.     public static void main(String args[]) {  
  15.         GenericsFoo<String> strFoo = new GenericsFoo<String>("Hello Generics!");  
  16.         GenericsFoo<Double> douFoo = new GenericsFoo<Double>(new Double("33"));  
  17.         GenericsFoo<Object> objFoo = new GenericsFoo<Object>(new Object());  
  18.         System.out.println("strFoo.getX=" + strFoo.getX());  
  19.         System.out.println("douFoo.getX=" + douFoo.getX());  
  20.         System.out.println("objFoo.getX=" + objFoo.getX());  
  21.     }  
  22. }  

    運作結果:

    strFoo.getX=Hello Generics!

    douFoo.getX=33.0

    [email protected]

    和使用“Object泛型”方式實作結果的完全一樣,但是這個Demo簡單多了,裡面沒有強制類型轉換資訊。

   1.6 下面解釋一下上面泛型類的文法:

      1.6.1 使用<T>來聲明一個類型持有者名稱,然後就可以把T當作一個類型代表來聲明成員、參數和傳回值類型。

      1.6.2 class GenericsFoo<T> 聲明了一個泛型類,這個T沒有任何限制,實際上相當于Object類型,

            實際上相當于 class GenericsFoo<T extends Object>。

      1.6.3 與Object泛型類相比,使用泛型所定義的類在聲明和構造執行個體的時候,

            可以使用“<實際類型>”來一并指定泛型類型持有者的真實類型。類如

        GenericsFoo<Double> douFoo=new GenericsFoo<Double>(new Double("33"));

      1.6.4 也可以在構造對象的時候不使用尖括号指定泛型類型的真實類型,但是你在使用該對象的時候,就需要強制轉換了。

            比如:GenericsFoo douFoo=new GenericsFoo(new Double("33"));

      1.6.5 當構造對象時不指定類型資訊的時候,預設會使用Object類型,這也是要強制轉換的原因。

2. 限制泛型

   2.1 在上面的例子中,由于沒有限制class GenericsFoo<T>類型持有者T的範圍,實際上這裡的限定類型相當于Object,

       這和“Object泛型”實質是一樣的。限制比如我們要限制T為集合接口類型。隻需要這麼做:

       class GenericsFoo<T extends Collection>,這樣類中的泛型T隻能是Collection接口的實作類,

       傳入非Collection接口編譯會出錯。

           注意:<T extends Collection>這裡的限定使用關鍵字extends,後面可以是類也可以是接口。

           但這裡的extends已經不是繼承的含義了,應該了解為T類型是實作Collection接口的類型,或者T是繼承了XX類的類型。

      下面繼續對上面的例子改進,我隻要實作了集合接口的類型:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class CollectionGenFoo<T extends Collection> {  
  2.     private T x;  
  3.     public CollectionGenFoo(T x) {  
  4.         this.x = x;  
  5.     }  
  6.     public T getX() {  
  7.         return x;  
  8.     }  
  9.     public void setX(T x) {  
  10.         this.x = x;  
  11.     }  
  12. }  

      執行個體化的時候可以這麼寫:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class CollectionGenFooDemo {  
  2.     public static void main(String args[]) {  
  3.         CollectionGenFoo<ArrayList> listFoo = null;  
  4.         listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());  
  5.         // 出錯了,不讓這麼幹。  
  6.         // 原來作者寫的這個地方有誤,需要将listFoo改為listFoo1  
  7.         // CollectionGenFoo<Collection> listFoo1 = null;  
  8.         // listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());  
  9.         System.out.println("執行個體化成功!");  
  10.     }  
  11. }  

    目前看到的這個寫法是可以編譯通過,并運作成功。可是注釋掉的兩行加上就出錯了,

    因為<T extends Collection>這麼定義類型的時候,就限定了構造此類執行個體的時候T是确定的一個類型。

3. 通配符泛型

   3.1 為了解決類型被限制死了不能動态根據執行個體來确定的缺點,引入了“通配符泛型”。

   3.2 針對上面的例子,使用通配泛型格式為<? extends Collection>,“?”代表未知類型,

       這個類型是實作Collection接口。那麼上面實作的方式可以寫為:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class CollectionGenFooDemo {  
  2.     public static void main(String args[]) {  
  3.         CollectionGenFoo<ArrayList> listFoo = null;  
  4.         listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());  
  5.         // 現在不會出錯了  
  6.         // 原來作者寫的這個地方有誤,需要将listFoo改為listFoo1  
  7.         CollectionGenFoo<? extends Collection> listFoo1 = null;  
  8.         listFoo1 = new CollectionGenFoo<ArrayList>(new ArrayList());  
  9.         System.out.println("執行個體化成功!");  
  10.     }  
  11. }  

      3.2.1 如果隻指定了<?>,而沒有extends,則預設是允許Object及其下的任何Java類了。也就是任意類。

      3.2.2 通配符泛型不單可以向下限制,如<? extends Collection>,還可以向上限制,

            如<? super Double>,表示類型隻能接受Double及其上層父類類型,如Number、Object類型的執行個體。

      3.2.3 泛型類定義可以有多個泛型參數,中間用逗号隔開,還可以定義泛型接口,泛型方法。

4. 泛型方法

   4.1 是否擁有泛型方法,與其所在的類是否泛型沒有關系。要定義泛型方法,隻需将泛型參數清單置于傳回值前。

   如:

[java] view plain copy print ?

11-java泛型總結
11-java泛型總結
  1. public class ExampleA {  
  2.     public <T> void f(T x) {  
  3.         System.out.println(x.getClass().getName());  
  4.     }  
  5.     public static void main(String[] args) {  
  6.         ExampleA ea = new ExampleA();  
  7.         ea.f(" ");  
  8.         ea.f(10);  
  9.         ea.f('a');  
  10.         ea.f(ea);  
  11.     }  
  12. }  

    輸出結果:

    java.lang.String

    java.lang.Integer

    java.lang.Character

    ExampleA

   4.2 使用泛型方法時,不必指明參數類型,編譯器會自己找出具體的類型。泛型方法除了定義不同,調用就像普通方法一樣。

   4.3 一個static方法,無法通路泛型類的類型參數,是以,若要static方法需要使用泛型能力,必須使其成為泛型方法。

總結:java1.5的新特性,之前了解到在java的發展中,1.5是個重大的分水嶺。泛型的嚴格檢查機制,極大的提高了程式的安全性,簡化了代碼,避免了強制類型轉換的問題。