1. 泛型類
泛型類就是具有一個或者多個類型變量的類,在Java集合框架中大量使用了泛型類。通過泛型編程可以使編寫的代碼被很多不同的類型所共享,大大提高了代碼的重用性。
下面給出一個自定義泛型類的例子:
public class Pair<T> { private T first; private T second; public Pair(T first,T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T first) { this.first = first; } public void setSecond(T second) { this.second = second; } }
使用普通的類名替換類型變量T就可以實例化泛型類型,如:Pair<String>,Java的泛型類類似於C++的模板類。
2. 泛型方法
Java還可以定義帶有類型參數的方法,即泛型方法,泛型方法可以定義在泛型類中,也可以定義在普通類中。
public class ArrayHelper { public static <T> T getMiddle(T[] array) { return array[array.length / 2]; } }
上述的泛型方法,參數是泛型數組,返回值是泛型變量,在修飾符后面跟有<T>表示這是泛型方法。調用一個泛型方法時在方法名前的"<>"加入具體類型,如:ArrayHelper.<String>getMiddle(new String[]{"left","middle","right"}) ,其實大多數情況下也可以省略<String>。
3. 類型變量的限定
有些時候,我們希望能使用不同的類型,但又希望這類型能滿足某些約束條件,這就要依靠對類型變量的限定。
public class ArrayHelper { public static <T> T max(T[] array) { T max = array[0]; for(int i = 1; i < array.length; i++) if(array[i].compareTo(max) > 0) max = array[i];
return max; } }
我們希望使用compareTo方法來比較泛型數組中的每個元素,從而選擇出最大的那個元素,而這就要求類型必須實現了Comparable接口,我們就可以對類型變量T作出如下限定:
public class ArrayHelper { public static <T extends Comparable> T max(T[] array) { T max = array[0]; for(int i = 1; i < array.length; i++) if(array[i].compareTo(max) > 0) max = array[i];
return max; } }
一個類型變量可以有多個限定,如: <T extends Comparable & Serializable> 。
4. 類型擦除
我們定義一個泛型類型后,就可以適配多種不同的類型,然而實際上虛擬機只知道一個原始類型,例如,對於上面定義個Pair<T>,其對應的原始類型如下:
public class Pair { private Object first; private Object second; public Pair(Object first,Object second) { this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object first) { this.first = first; } public void setSecond(Object second) { this.second = second; } }
即將T替換成了Object類,實際上是將T替換成限定的類型。假設<T extends Comparable>,則就會將T替換成Comparable,如果有多個限定類型,則替換成第一個限定類型。如果沒有限定類型,就替換成Object類,這個過程即類型擦除。
所以泛型類編譯成字節碼后就是一個普通的類。
5. 泛型類的繼承規則
(1)假設有一個print方法打印雇員對,參數是Pari<Employee>
public void print(Pair<Employee>) { ..... }
Manager類是Employee類的子類,那么Pair<Manager>是Pair<Employee>的子類么?,可以傳入print方法么?答案是不行,Pair<Manager>不是Pair<Employee>的子類。
(2) 永遠可以將參數化類型轉換成原始類型,如:Pair pair = new Pair<Manager>("Jack","Mike"); 這是為了與泛型之前的遺留代碼能夠保持銜接。
(3)泛型類可以像普通類一樣繼承其他類,實現接口。如: class Pair<T> implements Comparable 。
6. 通配符類型
Pair<? extends Fruit> 表示任何泛型Pair類型,它的類型參數是Fruit的子類。Pair<Fruit>和Pair<Apple>都是Pair<? extends Fruit>的子類型。
Pair<? super Apple>表示任何泛型Pair類型,它的類型參數是Apple的父類。Pair<Fruit>和Pair<Object>都是Pair<? super Apple>的子類型。
這樣,就可以利用參數多態了,修改上面的print方法:
public void print(Pair<? extends Fruit>) { ..... }
現在就可以傳入Pair<Apple>和Pair<Banana>等作為參數了。
但是對於通配符類型的多態,使用父類變量引用子類實例時,需要注意以下的問題:
Pair<Apple> apples = new Pair<Apple>(new Apple("apple1"),new Apple("apple2")); Pair<? extends Fruit> fruits = apples; //下面兩句調用setFirst方法編譯報錯,因為編譯器只知道Pair中保存類型的是Fruit的子類,但不知道具體是什么類型。 fruits.setFirst(new Apple("apple3")); fruits.setFirst(new Banana("banana1")); //下面調用getFirst方法不會出錯,因為編譯器知道Pair中保存的類型一定是Fruit的子類,轉換成Fruit類不會出錯。 Fruit first = frutis.getFirst();
即對於<? extends Type> 通配符類型,使用父類變量引用子類實例時,不能對子類實例進行寫,只能讀。
Pair<Apple> apples = new Pair<Apple>(new Apple("apple1"),new Apple("apple2")); Pair<? super Apple> fruits = apples; fruits.setFirst(new GoodApple("apple3")); //這一句調用setFirst方法不會出錯,因為編譯器知道Pair中保存的類型一定是Apple類的父類,因此,傳入Apple類對象或者Apple類的子類對象都是可以的
fruits.setFirst(new Fruit("banana1")); //傳入Apple類的父類對象就會編譯錯誤
Fruit first = frutis.getFirst(); //編譯不通過,因為編譯器知道Pair中保存的類型是Apple類的父類,但不知道具體是什么類,因此,只能賦值給Object類的變量
即對於<? super Type> 通配符類型,使用父類變量引用子類實例時,不能對子類實例進行讀,只能寫。
參考資料 《Java核心技術》