簡單泛型類
public class Pair<T> {
private T first;
private T second;
public Pair(){
first = null;
second = null;
}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
提示:類型變量通常使用大寫形式,且比較短。在Java庫中,使用變量E表示集合的元素類型,K和V分別表示表的關鍵字與值的類型。T(需要進使用U和S)表示“任意類型”
泛型方法
class ArrayAlg{
public static <T> T getMiddle(T[] a){
return a[a.length / 2];
}
}
泛型方法可以定義在普通類中,也可以定義在泛型類中。
類型變量的限定
class ArrayAlg{
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a == null || a.length == 0)
return null;
T min = a[0];
T max = a[0];
for(int i = 1; i < a.length; i++){
if(min.compareTo(a[i]) > 0) min = a[i];
if(max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<T>(min, max);
}
}
以上代碼中,將T限制爲實現了Comparable接口的類。如:
public static void main(String[] args) {
Integer[] i = new Integer[]{12,345,563,24,64};
Pair<Integer> pair = ArrayAlg.minmax(i);
System.out.println(pair.getFirst() + " " + pair.getSecond());
}
如果將上面代碼中第二行修改爲int[] i = new int[]{12,345,563,24,64};,則第三行出現編譯錯誤。
一個類型變量或通配符可以有多個限定,如:
T extends Comparable & Serializable
泛型代碼和虛擬機
無論何時定義一個泛型類型,都自動提供原始類型。原始類型的名字就是刪去類型參數後的泛型類型名。擦除類型變量,並替換爲限定類型。
原始類型用第一個限定的類型變量來替換。如果沒有給定限定就用Object替換。如Pair<T>中用Object替換 T,
注意:class Interval<Serializable & Comparable>中,原始類型用Serializable替換T,而編譯器在必要時向Comparable插入強制類型轉換。爲了提高效率,應該將標籤接口(即沒有方法的接口)放在邊界列表的末尾。
當程序調用泛型方法時,如果擦除返回類型,編譯器插入強制類型轉換。如:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst的返回類型後將返回Object類型。編譯器自動插入Employee的強制類型轉換。當存取一個泛型域時也要強制類型轉換。如
Employee buddy = buddies.first;
方法public static <T extends Comparable> T min(T[] a)擦除後爲public static Comparable min(Comparable[] a);
注意:方法擦除將帶來兩個複雜問題,請查看Java核心技術
Java泛型轉換:
虛擬機中沒有泛型 ,只有普通的類和方法
所有的類型參數都用到它們的限定類型替換
橋方法(查看Java核心技術)被合成保持多態
爲保持類型安全性,必要時插入強制類型轉換
約束與侷限性
1.不能用基本類型實例化類型參數
只要8種基本類型,當包裝類型不能接受替換時,可以使用獨立的類和方法處理它們。
2.運行時類型查詢只適用於原始類型
Pair<Integer> a = new Pair<Integer>();
a instanceof Pair<String>;
上面代碼第2行將報錯。
3.不能拋出也不能捕獲泛型類實例
泛型類擴展Throwable都不合法。如public class Problem<T> extends Exception{ } //ERROR--不能通過編譯。
不能在catch子句中使用類型變量,如
public static <T extends Throwable> void dowork(Class<T> t){
try{
do work
}
catch(T e){//編譯錯誤 -- 不能捕獲這種類型
}
}
但,在異常聲明中可以使用類型變量,如:
public static <T extends Throwable> void doWork(T t) throws T{//OK
try{
do work
}
catch(Throwable realCause){
t.initCause(realCause);
throw t;
}
}
4.參數化類型的數組不合法
不能聲明參數化類型的數組,如:Pair<String>[] table = new Pair<String>[10]; //ERROR
5.不能實例化類型變量
6.泛型類的靜態上下文中類型變量無效
public class Singleton<T> {
public static T getSingInstance(){
}
}
上面代碼中方法getSingInstance無法通過編譯。
7.注意擦除後的衝突
public class Pair<T> {
public boolean equals(T value){
return first.equals(value)&&second.equals(value);
}
}
從概念上講,Pair有兩個equals方法:
boolean equals(T)//定義在Pair<T>
boolean equals(Object)//從Object中繼承
方法擦除將導致兩個方法相沖突。可以重新命名引發錯誤的方法。
通配符類型(用於方法參數)
通配符Pair<? extends Employee>表示任何任何Pair類型,它的類型參數是Employee的子類,如Pair<Manager>,但不能是Pair<String>
注意:類型Pair<Manager>是Pair<? extends Employee>的子類型,而ArrayList<Manager>並不是ArrayList<Employee>的子類
如:
public static void printBuddies(Pair<? extends Employee> p){
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first + " " + second);
}
可以這樣調用 :printBuddies(new Pair<Manager>());
超類型限定:? super Manager這個通配符限制爲Manager的所有超類型
注意:當從超類型限定通配符中取出值時,只能將它賦給Object,如:Pair<? super Manager> p通配符中,Object o = p.getFirst();因爲調用getFirst,返回的對象類型得不到保證。
直觀地說,帶有超類限定的通配符可以向泛型對象寫入,如Pair<? super Manager> p中,可以這樣調用:p.setFirst(new Manager());而帶有子類型限定的通配符可以從泛型對象讀取,如Pair<? extends Employee> p通配中,可以這樣調用:Employee first = p.getFirst();
無限定通配符Pair<?>有方法:
? getFirst();
void setFirst();
而getFirst的返回值只能賦給一個Object,而setFirst方法不能被調用,甚至不能用Object調用。注意:Pair可以用任意Object對象調用原始的Pair類的setFirst方法。
無限定通配符對於很多簡單的操作非常有用。如,下面這個方法將用來測試一個Pair是否包含了指定的對象,它不需要實際的類型
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
通配符捕獲
有時我們需要使用通配符中的類型,如編寫 一個交換Pair元素的方法public static void swap(Pair<?> p),但?不能作爲一個類型,就是說
? t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
上面代碼無法通過編譯。
但我們可以通過一個輔助方法來完成工作,完整代碼如下:
public static void swap(Pair<?> p){
swapHelper(p);
}
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
反射和泛型
Class類是泛型的。如String.class是Class<String>的唯一對象。類型參數十分有用。
public static <T> T createInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException{
return clazz.newInstance();
}
爲了表達泛型類型聲明,Java SE 5.0在java.lang.reflect包中提供了一個新的接口Type,包括
Class類,描述具體類型
TypeVariable接口,描述類型變量
WildcardType接口,描述通配符
ParameterizedType接口,描述泛型類型或接口類型
GenericArrayType接口,描述泛型數組。
具體情況請查閱API