Java泛型(generics)是JDK 5中引入的一個新特性,允許在定義類/接口/方法的時候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在使用時用具體的類型來替換。
1. 為什么使用泛型
泛型的好處在于編寫重用性更好的代碼,用來指定容器要持有什么類型的對象,由編譯器保證類型的正確性,不需要強制類型轉(zhuǎn)換。
2. 基本使用
2.1 泛型術(shù)語
- List<E>被稱作泛型類型
- List<E>中的E被稱為類型變量或類型參數(shù)
- List<String>被稱為參數(shù)化類型。
- List<String>中的String被稱為實際類型參數(shù)。
- List被稱為原始類型。
2.2 泛型類
創(chuàng)建類時,指明想持有的對象,將其置于尖括號之內(nèi)。常見使用情景元組類庫:是一個單一對象,將一組對象直接打包存儲于其中。這個容器對象允許讀取其中的元素,但是不允許向其中存放新的對象。
public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {
first = a;
second = b;
}
}
泛型接口定義類似于泛型類在這里不具體描述。
2.3 泛型方法
泛型方法使得該方法能夠獨立于類而產(chǎn)生變化,建議在使用泛型方法可以取代整個類泛型話的時候選擇泛型方法。定義泛型方法,只需要泛型參數(shù)列表置于返回值之前。
public class Generators{
public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gne, int n){
for(int i = 0; i < n;i++){
coll.add(gen.next());
}
return coll;
}
}
泛型方法使用注意點:
- 定義方法所用的泛型參數(shù)需要在修飾符之后添加,如 public static <T>,如果有多個泛型參數(shù),可如此定義<K,V>。
- 類型參數(shù)推斷,區(qū)別于泛型類在使用時候必須指定類型參數(shù)值,泛型方法則不必,編譯器可以為我們找出具體類型,可以像調(diào)用普通方法一樣調(diào)用fill()方法。
3. 泛型擦除
java的泛型是在編譯器層面實現(xiàn)的,生成的字節(jié)碼中是不包含泛型中的類型信息的
在JAVA的虛擬機中并不存在泛型,泛型只是為了完善java體系,增加程序員編程的便捷性以及安全性而創(chuàng)建的一種機制,在JAVA虛擬機中對應泛型的都是確定的類型。
在編寫泛型代碼后,java虛擬中會把這些泛型參數(shù)類型都擦除,用相應的確定類型來代替,代替的這一動作叫做類型擦除,而用于替代的類型稱為原始類型,在類型擦除過程中,一般使用第一個限定的類型來替換,若無限定則使用Object。
有界類擦除前:
public class Eraser {
public static <A extends Comparable<A>> A max(Collection<A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
}
有界類擦除后:
public class Eraser {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable)xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
}
類型參數(shù)的邊界<A extends Comparable<A>>A,A必須為Comparable<A>的子類,按照類型擦除的過程,先將所有的類型參數(shù)轉(zhuǎn)換為最左邊界Comparable<A>,然后去掉參數(shù)類型A,得到最終的擦除后結(jié)果。
多邊界擦除
規(guī)則:使用其最左限制的擦除,即用第一個邊界的類型變量來替換。
如果類Pair聲明如下:
public class Pair<T extends Comparable & Serializable> { }
那么原始類型就是Comparable
4. 邊界與通配符
邊界使得你可以用于泛型的參數(shù)類型上設置限制條件,潛在效果你可以按照自己邊界類型調(diào)用方法。
4.1 邊界定義
<T extends BoundingType>這表示T應該是BoundingType的子類型,T和BoundingType可以是類,也可以是接口。另外,此處的extends表示的是子類型,不等同于繼承。多個邊界格式為:<T extends A & B & C>
示例代碼中T可以在編譯器調(diào)用HasColor擁有的方法。
class Colored<T extends HasColor> {
T item;
Colored(T item) {
this.item = item;
}
T getItem() {
return item;
}
//定義邊界之后,如下的方法調(diào)用是合法的
java.awt.Color color() {
return item.getColor();
}
}
4.2 邊界限定通配符
- <? extends T> 表示類型的上界,表示參數(shù)化類型的可能是T或是T的子類。
- <? super T> 表示類型下界,表示參數(shù)化類型是此類型的超類型(父類型),直至Object
- <?> 表示類型無界,常見使用多個泛型參數(shù)允許其中某些為任何類型,例如:Map<String,?>
使用原則參考《Effective Java》:
- 如果要從集合中讀取類型T的數(shù)據(jù),并且不能寫入,可以使用 ? extends 通配符;(Producer Extends)
- 如果要從集合中寫入類型T的數(shù)據(jù),并且不需要讀取,可以使用 ? super 通配符;(Consumer Super)
- 如果既要存又要取,那么就不要使用無界通配符。
// demo1使用上界寫入會編譯錯誤
List<? extends Fruit> frutis = new ArrayList<Apple>();
fruits.add(new Apple());// complie error
fruits.add(new Fruit());// complie error
// demo2使用下界寫入正常
List<? super Apple> frutis = new ArrayList<Apple>();
fruits.add(new Apple());
對于demo1,因為List<? extends Fruit> 表示 “具有任何從Fruit繼承類型的列表”,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象。
對于demo2,list持有的是以Apple為下界,所以編譯器可以明確知道添加Apple或Apple的子類是安全的。
5. 注意問題
- 不能用基本類型實例化類型參數(shù)
- 不能實例化類型變量,例如new T(),T.class等
- List.class,String[].class,int.class都是允許的,但就List<String>.class和List<?>.class則不合法
- <Fruit> 和 <Apple>之間沒有繼承關系,所以List<Fruit>= new ArrayList<Apple>;會報編譯錯誤的,上界和下界為了解決該問題引入。
- 類型安全和異構(gòu)容器: 一個集合如果可以預測返回值的類型,則是類型安全的;如果這個集合的鍵并不是同一個類型的,則稱之為異構(gòu)的。通過包裝一個Map,將其中的key保存為Class的,value保存為Object的,可以實現(xiàn)一個異構(gòu)容器。
參考文檔
http://blog.csdn.net/explorers/article/details/454837
http://hongjiang.info/java-generics/
http://www.importnew.com/19740.html