泛型介紹
泛型程式設計 :可以被很多不同型別的物件所重用。比那些直接使用Object變數,然後強制型別轉換的程式碼具有跟好的安全性和可讀性。
使用型別引數可以將需要使用的型別,提前宣告
ArrayList<String> newlist = new ArrayList<String>();複製程式碼
使用型別引數可以告知這個類適用於什麼型別,當呼叫對應的get()方法的時候,不需要進行強制型別轉換,編譯器本事就知道其對應的型別。
當實現一個泛型的時候非常不容易,因為你需要知道這個這個類對應的所有用途及其型別,所以java提供了萬用字元型別,來解決這個問題。
定義簡單泛型類
型別變數使用大寫形式,且比較短,這是很常見的。在java庫中,使用變數E表示集合的元素型別,K和V分別表示表的關鍵字和值的型別。T(需要時還可以用臨近的字母U和S)表示型別。
泛型類,就是指具有一個或者多個型別變數,也就是說這個類適應這幾種型別,對於之後在那類來說,我們只關注泛型,而不會為資料村吃的細節煩惱。
使用型別變數T,用<>括起來,放在類名後面。這個泛型可以有多個型別變數,如<T,U>
可以使用類定義的型別變數指定類中屬性和方法的型別。
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,說明該方法是泛型方法
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
不能建立引數化型別的陣列
不能丟擲也不能捕獲泛型類例項
public class GenericException <T> extends Exception {...}
這種泛型類擴充套件子Throwable是不合法的,不能通過編譯器。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 ;
}
}
複製程式碼
class Calendar implements Comparable<Calendar> {...}
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> {...} 複製程式碼