java 泛型程序設計

泛型介紹

泛型程序設計 :能夠被不少不一樣類型的對象所重用。比那些直接使用Object變量,而後強制類型轉換的代碼具備跟好的安全性和可讀性。java

使用類型參數能夠將須要使用的類型,提早聲明數組

ArrayList<String> newlist = new ArrayList<String>();複製代碼

使用類型參數能夠告知這個類適用於什麼類型,當調用對應的get()方法的時候,不須要進行強制類型轉換,編譯器本事就知道其對應的類型。安全

當實現一個泛型的時候很是不容易,由於你須要知道這個這個類對應的全部用途及其類型,因此java提供了通配符類型,來解決這個問題。
bash

定義簡單泛型類

類型變量使用大寫形式,且比較短,這是很常見的。在java庫中,使用變量E表示集合的元素類型,K和V分別表示表的關鍵字和值的類型。T(須要時還能夠用臨近的字母U和S)表示類型。dom

泛型類,就是指具備一個或者多個類型變量,也就是說這個類適應這幾種類型,對於以後在那類來講,咱們只關注泛型,而不會爲數據村吃的細節煩惱。ui

使用類型變量T,用<>括起來,放在類名後面。這個泛型能夠有多個類型變量,如<T,U>this

可使用類定義的類型變量指定類中屬性和方法的類型。spa

public class Pari<T> {  
  
    private T first;  
    private T second;  
      
    public Pari(){  
        first = null;  
        second = null;  
    }  
    public Pari(T first,T second){  
        this.first = first;  
        this.second = second;  
    }  
      
    public T getFirst(){  
        return first;  
    }  
      
    public T getSecond(){  
        return second;  
    }  
      
    public void setFirst(T value){  
        first = value;  
    }  
    public void setSecond(T value){  
        second = value;  
    }  
} 
複製代碼

 Pair類引入了一個類型變量T,用尖括號(<>) 括起來, 並放在類名的後面;
泛型類能夠有多個類型變量, 如, 定義 Pair 類, 其中第一個和第二個域使用不一樣的類型

其實泛型類能夠看作是普通類的工廠。設計

泛型方法

泛型方法既能夠在普通類中,也能夠在泛型類中,定義方式是在方法名前加<T> T,說明該方法是泛型方法code

class ArrayAlg
{
    public static<T> T getMiddle(T... a)
    {
        return a[a.length / 2];
    }
}
複製代碼

當調用一個泛型方法時, 在方法名前的尖括號中放入具體的類型

String middle = ArrayAlg.<String>getMiddle("john", "Q.", public ");複製代碼

方法調用中能夠省略 類型參數, 編譯器有足夠的信息能偶推斷出所調用的方法;

也就是說, 能夠調用

String middle = ArrayAlg.getMiddle("john", "Q.", public ");複製代碼

 對於泛型方法的引用都沒有問題。 偶爾, 編譯器也會提示錯誤,

double middle = ArrayAlg.getMiddle(3.14, 0, 1729);1複製代碼
編譯器將會自動打包參數爲 1個 Double 和 2個Integer 對象,然後尋找這些類的共同超類型。
事實上, 找到2個這樣的超類型:Number 和 Comparable 接口, 其自己也是一個泛型類型。 在這種狀況下, 能夠採起的補救措施是 將 全部的參數寫爲 double 值;

類型變量的限定

有的時候,好比對於特定的方法執行特定的操做,可是該操做不適用於一些類型,這時能夠對類型變量T設置限定,可使其集成特別的類或者接口(沒錯,在這裏對於接口也是使用繼承,由於使用extends更加接近子類的意思)

一個類型變量或通配符能夠有多個限定,限定類型用「」&「」 分隔,而用逗號用來分隔類型變量。在java繼承中,能夠根據須要擁有多個接口超類型,但限定中至多有一個類。若是用一個類做爲限定,但必須是第一個。

好比:T extends Comparable & Srializable

public static <T extends Comparable> Pari<T> getMinMax(T[] word){  
      
    if(word == null || word.length == 0)  
        return null;  
    T min = word[0];  
    T max = word[0];  
    for(int i=1;i<word.length;i++){  
        if(word[i].compareTo(max) > 0)  
            max = word[i];  
        if(word[i].compareTo(min) < 0)  
            min = word[i];  
    }  
    return new Pari<T>(min,max);  
}
複製代碼

JVM中沒有泛型,只有普通的類和方法

全部的類型參數都是用他們的限定類型轉換(若是沒有類型參數,則使用Object類型),這個過程稱爲擦除(erased),擦除類型變量,並替換爲限定類型

有時爲保持類型安全性,須要插入強制類型轉換

約束與侷限性

不能用基本類型實例化類型參數

不能用類型參數來代替基本類型。就是沒有Pair<double>,只有Pair<Double>。固然主要是緣由是類型擦除。擦除以後,Pair類含有Obkect類型的域,而Object不能存儲double的值。

運行時類型查詢只適用於原始類型

虛擬機中的對象總有一個特定的非泛型類型。所以,全部類型查詢只產生原始類型。

如:if(a instanceof Pari<String>)是錯誤的,由於只能查詢原始類型,即Pari,if(a instanceof Pari<T>)是錯誤的

又如:

Pari<String> pari1 = (Pari<String>) a

不管什麼時候使用instanceod或者設計泛型類型的強制類型轉換表達式都會看到一個編譯器警告。

一樣道理,getClass方法老是返回原始類型。

if (pari1.getClass() == pari2.getClass())返回true,由於兩次getClass()都是返回Pari.class

不能建立參數化類型的數組

Couple<Employee>[] couple = new Couple<Employee>[5] ;這種聲明式不合法的,這裏面有一個問題仍是經過類型擦除機制來解釋,類型擦除後couple的類型是Couple[],考慮一下兩種賦值方式:
1.couple[0] = "wife"時,編譯器會報錯,這個很明顯的錯誤,couple每一個元素都是Couple類型。
2.couple[0] = new Couple<String>(),類型擦除後這個能夠經過數組檢測,但仍然是錯誤的類型,由於couple在聲明的時候定義的是Couple<Employee>,因此會出現問題。
若是要存放參數化類型對象的集合,能夠考慮使用ArrayList<Couple<Employee>>進行聲明,並且Java中建議優先使用集合,這樣既安全又有效。

不能拋出也不能捕獲泛型類實例

 在Java中, public class GenericException <T> extends Exception {...} 這種泛型類擴展子Throwable是不合法的,不能經過編譯器。
不能再catch子句中使用類型參數,如:
public static <T extends Throwable> void doWork(Class<T> t) {  
     try {  
           // do work...  
     } catch (T e) {  
           e.printStackTrace();  
     }  
  }  // 錯誤 複製代碼

而這個是合法的

public static <T extends Throwable> void doWork(T t) throws T {  
          try {  
                    // do work...  
         } catch (Throwable e) {  
                   e.printStackTrace();  
                    throw t;  
         }  
}// 正確  複製代碼

java 異常處理的一個基本原則是,必須爲全部已檢查異常提供一個處理器。不過能夠利用泛型消除這個限制。

不能實例化類型變量 

不能使用像new<T>(...),new t[...]或T.class這樣的表達式中的類型變量。例如,下面的Pair<T>構造器就是非法的:

public Pair()
{
first =new T();
 second=new T ();
}//ERROR

複製代碼

類型擦除將T改變成Object。並且本意確定不但願調用newObject(),可是能夠經過反射調用Class.newInstance方法來構造泛型對象。

遺憾的是T.class在Java中也是不被支持使用的,因此一種彌補的方式,傳入一個類型闡述爲T的Class對象,如Class<T>

public static <T> Couple<T> createInstance(Class<T> clazz) {  
          try {  
                    return new Couple<T>(clazz.newInstance(), clazz.newInstance());  
         } catch (Exception e) {  
                    return null ;  
         }  
}  
複製代碼

     初學者對Java反射不熟悉不用着急,這裏只要知道不能實例化類型參數便可,同理,不能實例化一個泛型數組,如 

public static <T> T[] maxTwo(T[] values) {
    T[] array = new T[2];
} // 錯誤   

複製代碼

     泛型構建數組是不合法的,由於這麼構建在擦除以後構造的永遠是new Object[2],這不是咱們所但願的結果。並且這樣會致使一些運行時錯誤。爲了更進一步說明隱患問題,來看看下面代碼: 

public static <T extends Comparable<T>> T[] maxTwo(T[] array) {  
     Object[] result = new Object[2];  
     return (T[]) result; // Type safety: Unchecked cast from Object[] to T[]  
}  
複製代碼

     這種方式會產生變異警告:Object[]轉換爲T[]是沒有被檢查的。咱們來試一試這個調用: maxTwo(new String[] { "5", "7" , "9" });,運行後,發生了類型轉換異常,由於方法在調用的時候將Object[]轉換爲String[],失敗的類型轉化。怎麼解決呢?一樣這裏可使用Java發射來解決: 

public static <T extends Comparable<T>> T[] maxTwo(T[] array) {  
     // Type safety: Unchecked cast from Object[] to T[]  
     return (T[]) Array.newInstance(array.getClass().getComponentType(), 2) ;  
}  
複製代碼

泛型類的靜態上下文中類型變量無效 

不能在靜態域或方法中引用類型變量。

public class Singleton<T>
{
    public static T singleInstacne;//ERROR
    public static T getSingleInstance();//ERROR
    {
        if(singleInstacne==null)
        return singleInstance;
    }
}複製代碼

若是這個程序可以運行,就能夠聲明一個Singleton<Random>共享隨機數生成器,聲明一個Singleton<JFileChooser>共享文件選擇器對話框。可是,這個程序沒法工做。類型擦除以後,只剩下Singleton類,他只包含一個singleInstance域。

注意擦除後的衝突

public class NameClash<T> {  
      public boolean equals(T value) {  
               return false ;  
     }  
}  
複製代碼

     從這個類的定義中來看,存在兩個equals方法,一個是自身定義的 public boolean equals(T value) {...},一個是從Object繼承的 public boolean equals(Object obj) {...},但類型擦除之後,前者方法成爲了 public boolean equals(Object value) {...},而在一個類中同時存在兩個方法名和參數同樣的方法是不可能的,因此這裏引起的衝突是無法經過編譯器的。能夠經過從新命名方法進行修正。
擦除引發的衝突還體如今另外一點上,再看一段錯誤的代碼:

class Calendar implements Comparable<Calendar> {...}  
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> {...}  複製代碼
上述代碼是非法的,爲何?回顧一下類型擦除後,虛擬機會爲
Calendar
類合成橋方法,實現了Comparable<Calendar>得到一個橋方法:
public int compareTo (Object o) { return compareTo((Calendar)o);}
而實現了Comparable<GregorianCalendar >在類型擦除後,虛擬機爲GregorianCalendar合成一個橋方法:
public int compareTo (Object o) { return compareTo((GregorianCalendar )o);}
這樣一來在GregorianCalendar類中存在兩個同樣的方法,這是不容許的。
相關文章
相關標籤/搜索