首頁 >php教程 >PHP开发 >泛型-泛型簡介

泛型-泛型簡介

高洛峰
高洛峰原創
2016-12-19 16:02:195181瀏覽

泛型簡介 

先拿一個例子來說明泛型是什麼。 
有兩個類別如下,要建構兩個類別的對象,並列印出各自的成員x。
public class StringFoo { 
    private String x; 
    public String getX() { 
       
        this.x = x; 
    } 


public class DoubleFoo {
    private Double x; 
    public Double getX() { 
        return x; 
     this.x = x; 
    } 

如果Integer、Long、Date等類型的操作,還要寫相應的類,實在無聊之極。 
因此,對上面的兩個類別進行重構,寫成一個類,考慮如下: 
上面的類別中,成員和方法的邏輯都一樣,就是類型不一樣。 Object是所有類別的父類,因此可以考慮用Object做為成員類型,這樣就可以實現通用了。
public class ObjectFoo { 
    private Object x; 
    public Object getX() { 
       
        this.x = x; 
    } 


所呼叫的程式碼如下: 
public class ObjectFooDemo { 
    public static void main(String args[]) { 
       ; 
        ObjectFoo douFoo = new ObjectFoo(); 
               douFoo .setX(new Double("33")); 
        ObjectFoo objFoo = new ObjectFoo(); 
            String str = (String)strFoo.getX(); 
        Double d = (Double)douFoo.getX(); 
        Object obj = objFoo.getX(); 
             System.out.println("douFoo.getX=" + d); 
        System.out.println("strFoo.getX=" + obj); 
    } 

以上,且沒有泛型的情況下,我們所寫的基礎聲明,然後將值傳入,取出時要進行強制型別轉換。 
JDK 從1.5 開始引入了泛型的概念,來優雅解決這類問題。採用泛型技術,寫的程式碼如下: 
public class GenericsFoo
    private T x; 
    public T getX() { public void setX(T x) { 
        this.x = x; 
    } 


所呼叫的程式碼如下: 
public class GenericsFooDemo { 
    public static void main(String args[ 
    public static void main(String args[])[ oo(); 
        strFoo.setX ("Hello Generics!"); 
        GenericsFoo douFoo=new GenericsFoo(); 
      objFoo=new GenericsFoo() ; 
        objFoo.setX(new Object()); 

        String str = strFoo.getX();N Object obj = objFoo.getX(); 
        
        System.out.println("str         System.out.println ("strFoo.getX=" + obj); 
    } 


注意,且有幾點明顯的變化: 
1.            物件建立時,以明確給予類型,如GenericsFoo。 
2.            物件以getX方法取出時,則不需要進行型別轉換。 
3.            個別方法的調用,而如果參數類型與建立時指定的類型不符時,編譯器就會錯誤。 
那我們為什麼要泛型呢? 有兩個好處: 
1. 可以在編譯時檢查儲存的資料是否正確。我們開發有一個趨向就是儘早的發現錯誤,最好就是在編譯階段, 泛型正好符合這個條件。
2. 減少了強制轉換, String str = (String)strList.get(0);這樣的操作屬於一種向下轉型, 是比較危險的操作, 當List內儲存的物件不適String時就會拋出異常。 
JDK1.5 中,java.util 套件中的各種資料類型工具類,都支援泛型,在程式設計上被廣泛使用,需要好好掌握。 
泛型最常見的應用是應用在類別、介面和方法上,以下分別介紹。
3.4.2       應用​​在介面上: 
public inter     泛型應用於介面: 
public inter     泛型應用於介面: 
public inter     泛型應用於介面: 
public inter String(); 

這裡A和B都是代表類型。尖角號中,可以使用一個類型,也可以使用多個類型。
3.4.3       泛型應用在類別中: 
public class ValuePairImpl
public final A first; 
public final B second 
public final A first; 
public final B second 
public final A first; 
public final B second 
public final A first; 
public final B second 
); b; } 
       public A getA() { return first; } 
       public B getB() { return second; } 
@ public public B getB() { return second; } 
+ "3 String toString(re);
  } 

如果這個類別實現泛型接口,則對應的寫法為: 
public class ValuePairImpl
  implements ValuePair { 🠎… 🠎}.4.41 上: 
泛型也可以應用在單獨的方法上,例子如下: 
public class GenericMethod { 
       public void printValue(T v) {  “ + v.toString(); 
              System.out.println(str); 


我注意語法:在public修飾當然,傳回值也可以是泛型的型別。 
3.4.5       限制泛型的可用類型 
以上所介紹的三種泛型應用,應用在介面、類別、方法上,是一種通用的做法,對泛型可傳入的類型沒有任何限制。但在某些場景下,我們希望對可用的類型進行限制,例如希望傳入的類型必須從某個類別繼承(也就是說,必須是某個類別的子類別、孫類別等),在這種情況下就用到了泛型限制的語法。 
extends:限制泛型類型必須為某一類別的後代,包括此類型。 
語法: 
這裡,T為泛型類型,extends 關鍵字限制泛型類型必須是parentClass的後代。 parentClass 指定父類別的類型,也可以是介面。
在Java語言中,對類別只能單繼承,對接口可以多繼承,如果要限制指定類型必須從某個類別繼承,並且實現了多個接口,則語法為: 
 
注意,類別必須在介面前面。 
舉例如下: 
public class BaseClass { 
       整數值; 

       public BaseClass(int value) { 
              this.value = value          this.value = value; 
       } 
       
       public int getValue() { 🎠     

       public void setValue(int value) {
              this.value = 價值; 
       } 
       


public class SubClass extends BaseClass{ 
        super(value*2); 
       } 


公用類別 GenericBound
              long iValue = 0; 
              for (BaseClass base : tList) { 
              }
              } 
              🎠   

       public static void main(String[] args) { 
              GenericBound ; obj = new
GenericBound(); 
              
             已的列表 list = new LinkedList (    列表 list = new LinkedList (); 
              list.add(new SubClass(5)); 
              list.add(new SubClass(6)); 
              
              System.out.println(obj.sum(list)); 
       } 

運行,輸出結果為22. 
接下來,我們再深入探討一下。  long iValue = 0; 
              for (BaseClass base : tList) { 
              
              } 
              
              返回 iValue; 
       } 

       public static void main(String[] args) { 
                      注意 // 
              // 將obj的類型由GenericBound改為GenericBound,無法編譯
              GenericBound; obj = new
GenericBound(); 
              
List list = new LinkedList(); 
              list.add(new SubClass(5));              
              System.out.println(obj.sum( list)); 
       } 

語句GenericBound obj = new GenericBound(); 無法透過編譯,其語句GenericBound obj = new GenericBound(); 無法透過編譯,其根本原因在於其中的時候T是確定的一個類型,這個類型是BaseClass的後代。但是BaseClass的後代還又很多,如SubClass3,SubClass4,如果針對每一種都要寫出具體的子類類型,那也太麻煩了,乾脆還不如用Object通用一下。能不能像普通類別那樣,用父類別的型別引入各種子類別的實例,這樣不就簡單了很多?答案是肯定的,泛型針對這種情況提供了更好的解決方案,那就是“通配符泛型”,下面詳細講解。 
3.4.6       通配符泛型 
Java 的泛型型別如 java.lang.String,java.io.File 一樣,屬於普通的 Java 型別。比方說,下面兩個變數的型別是互不相同的: 
Box boxObj = new Box(); 
Box boxStr = new Box(); 
雖然String 是Object的子類,但是Box 和Box 之間沒有關係-Box 不是Box 的子類別或子類型,因此,以下賦值語句是非法的: 
boxObj = boxStr;     // 無法透過編譯 
因此,我們希望使用泛型時,能像普通類別那樣,用父類別的類型引入各種子類別的實例,從而簡化程式的開發。 Java的泛型中,提供 ? 通配符來滿足這個要求。
程式碼範例如下: 
public class WildcardGeneric { 
       public void print(List>  lst) { 
                         System.out.println(lst. get(i)); 
              } 
       } 
                 WildcardGeneric wg = new WildcardGeneric(); 
               
              strList .add("One"); 
                
              LinkedListintList = new LinkedList(); 
        
              intList.add(30); 
                   } 

但在這種情況下,WildcardGeneric.print 方法的參數可以接受型別可能對於程式設計師設計的意圖太廣泛了一點。因為我們可能只是希望 print 可以接受一個List,但這個List中的元素必須是Number的後代。因此,我們要對通配符有所限制,這時可以使用邊界通配符(bounded wildcard)形式來滿足這個要求。我們再修改print 方法: 
public void print(List extends Number>  lst) { 
                     System.out.println(lst. get(i)); 
              } 
       }     為String>)傳給print 方法將是非法的。 
除了 ? extends上邊界通配符(upper bounded wildcard)以外,我們也可以使用下邊界通配符(lower bounded wildcard),例如 List super ViewWindow>。
最後總結一下使用通配符的泛型類型的三種形式: 
GenericType> 
GenericType extends upperBoundType> 
GenericType su 基本用法,接著再來探討一下深入的主題。
我們還是先來看一段程式碼: 
public class GenericsFoo
    private T x; 
    public T getX() {   public void setX(T x) { 
        this.x = x; 
    } 
    
    public static void main(String[] args) { 
          gf.setX("Hello"); 
          
          GenericsFoo> gf2 = gf; 
          gf2.setX("World");                 // 錯誤化! ! ! 
          String str = gf2.getX();          // 錯誤化! ! ! 
          gf2.setX(gf2.getX());             // 錯誤化! ! ! 
    } 

       請注意,且main 方法中的最後三排都是違法的,無法透過編譯。本來是一個的泛型,經由>來引用後,setX() 傳入一個String就報錯,getX() 回傳值的型別也不是String。更奇怪的是,語句gf2.setX(gf2.getX()); 就是從裡面取出值然後再原封不動設定回去,也不行。這是怎麼回事? 
       為了徹底弄清楚這些問題,我們需要了解JDK對泛型的內部實作原理。先看兩個例子: 
public class GenericClassTest { 
       public static void main(String[] args) { 
                 Class c2 = new ArrayList() .getClass(); 
              System.out.println(c1 == c2); 
           
        new ArrayList().getClass(); 
              System.out.println(c1 == c3); 
      } 

     運作後,輸出結果為: 
true 
true 
       這個範例說明,泛型ArrayList、ArrayList和沒有使用泛型的ArrayList 其實是同一個類別。就如同沒使用泛型一樣。
       再看第二個例子: 
class Element {} 
class Box {} 
class Pair {} 

public class GenericClassT. s) { 
List list = new ArrayList(); 
           Map map = new HashMap();         Pair p = new Pair(); 
           
                    list.getClass().getTypeParameters())); 
           System.out.println(Arrays .toString( 
                        map.getClass().getTypeParameters()));                       box.getClass().getTypeParameters())); 
   ( 
                        p.getClass().getTypeParameters()));  
[K, V] 
[T] 
[KEY, VALUE] 
查閱JDK的文檔,Class.getTypeParameters() 方法傳回一個TypeVariable物件的數組,數組中每個TypeVariable物件描述了泛型中聲明的類型。這似乎意味著,我們可以從TypeVariable 物件找出泛型實例化時真正的類型。但是,從程式運行的輸出結果,我們可以看出,Class.getTypeParameters() 返回的一系列TypeVariable 對象,僅僅表徵了泛型聲明時的參數化類型佔位符,真正實例化時的類型都被拋棄了。因此,Java 泛型的真相是: 
在泛型程式碼中,根本就沒有參數化類型的資訊。
產生這樣一個事實的原因在於,JDK 對泛型的內部實現,採用了擦除(erasure)的方式,具體擦除的方式如下: 
1)     ArrayList、ArrayList、ArrayList >都被擦除成ArrayList 
2)     ArrayList、ArrayList都被擦除成ArrayList 
3)     ArrayList 
3)     ArrayList 
3)     ArrayList在了解擦除的實作機制後,我們再回過頭來,分析前面的例子,看看為什麼不能透過編譯: 
public class GenericsFoo
    private T x; 
  x; 
    } 

    public void setX(T x) { 
        this.x = x;  ) { 
          GenericsFoo gf = new GenericsFoo() ; 
          gf.setX("Hello"); 
          
              gf2.setX("World");                 // 錯誤化! ! ! 
          String str = gf2.getX();   // 錯誤錯誤! ! ! 
          gf2.setX(gf2.getX());             // 錯誤化! ! !
    } 

由於擦除機制,GenericsFoo>都被擦除成GenericsFoo,類型被遺失後,那麼對應的方法宣告就變成了: 
public Object getX(); 
public v. ); 
因此,泛型類別的任何取出值的get方法,返回值都變成了Object,可以被調用,但返回值需要進行類型轉換;泛型類別的任何設定值的set方法,參數類型都變成了null,任何類型都無法轉換成null類型,因此所有的set方法都無法被呼叫。 
這樣就形成了一個有趣的現象:對>的泛型類型,只能get,不能set。 
以上闡述了Java泛型的擦除機制,導致一些有用的類型資訊遺失。但我們可以透過一些技巧,讓編譯器重新建構出類型訊息,使得set方法可以被正常呼叫。請參閱以下的程式碼: 
       public void setGeneric(GenericsFoo> foo) { 
              private void setGenericHelper(GenericsFoo foo) { 
        foo.setX(foo.getX( )); 
    } 
setGenericHelper() 是泛型方法,泛型方法引入了額外的類型參數(位於返回類型之前的尖括號中),這些參數用於表示參數和/或方法的傳回值之間的類型約束。 setGenericHelper () 這種宣告方式,允許編譯器(透過型別介面)對GenericsFoo 泛型的型別參數命名。但一個型別可以有父類、祖父類,還可以實作多個接口,那麼編譯器會轉換成哪個型別呢?
我們用下面這段程式碼來驗證一下: 
public class GenericClassTest3 { 
       public static String getType(T arg) { 
       } 
       
       public static void main( String[] args) { 
              Integer i = new Integer(5); 
          } 

程式運作後,輸出結果為: 
java.lang.Integer
因此,編譯器能夠推斷T 是Integer、Number、 Serializable 或Object,但它選擇Integer 作為滿足約束的最具體類型。
另外,由於泛型的擦除機制,我們也無法直接對泛型型別用new運算符,例如: 
public class GenericNew
       
    obj = new T() ;       // 無法通過編譯! ! ! 
              return obj; 
       } 

由於擦除機制,語句T obj = new ); 編譯了 
但是,在某些情況下,我們還是需要對泛型類型的動態實例化。建立單一物件和建立數組,程式碼範例如下: 
public class GenericNew
       
       public T                      Object obj = cls.newInstance(); 
      )obj; 
} catch(Exception e) { 
                     return null; 
         
       public T[] createArray(Class cls, int len) { 
              Object obj = java.lang.reflect.Array.newInstance( cls, len); 
                     return (T[])obj; 🎠                     return null; 
              } createArray 方法實作了動態建立一個泛型類型的實例陣列。 




更多泛型-泛型簡介相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn