一 泛型是什么
泛型最精準(zhǔn)的定義:參數(shù)化類(lèi)型。具體點(diǎn)說(shuō)就是處理的數(shù)據(jù)類(lèi)型不是固定的,而是可以作為參數(shù)傳入。定義泛型類(lèi)、泛型接口、泛型方法,這樣,同一套代碼,可以用于多種數(shù)據(jù)類(lèi)型。
二 泛型類(lèi)和泛型方法
2.1 泛型類(lèi)和接口
泛型類(lèi)和接口類(lèi)似,定義一個(gè)泛型類(lèi):
public class Som<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Som就是一個(gè)泛型類(lèi),value的類(lèi)型是T,而T是參數(shù)化的。如果有多個(gè)類(lèi)型參數(shù),使用分號(hào)隔開(kāi),如<U,V>。
使用泛型類(lèi):
Som<String> som = new Som<>();
som.setValue("Hi");
//som.setValue(123);編譯不通過(guò)
String str = som.getValue();
在使用中指定具體的類(lèi)型實(shí)參。
2.2 泛型方法
定義一個(gè)泛型方法:
public static <V> V obtainV(V[] arr) {
return arr[arr.length / 2];
}
obtainV就是一個(gè)泛型方法,返回值前有<V>,可以處理任意類(lèi)型數(shù)組。
使用泛型方法:
Integer [] arr = {1,2,3};
String [] arrStrs = {"1","2","3"};
int i = obtainV(arr);
String str = obtainV(arrStrs);
三 Java泛型的實(shí)現(xiàn)原理:類(lèi)型擦除
泛型是JDK1.5引入的,為了保持兼容,Java泛型的實(shí)現(xiàn)采用了類(lèi)型擦除。類(lèi)定義中的類(lèi)型參數(shù)會(huì)被替換為Object,運(yùn)行時(shí)不知道泛型的實(shí)際類(lèi)型參數(shù)。
編譯前代碼:
Som<String> som = new Som<>();
som.setValue("Hi");
String str = som.getValue();
編譯后生成的代碼:
Som som = new Som();
som.setValue("Hi");
String str = (String)som.getValue();
可以看到在使用泛型的地方,編譯后生成的代碼,編譯器自動(dòng)進(jìn)行了強(qiáng)制類(lèi)型轉(zhuǎn)換。
Java的泛型實(shí)現(xiàn)就是如此:在編譯期進(jìn)行泛型檢查,編譯后的代碼擦除了類(lèi)型信息,所有泛型都使用Object代替,并進(jìn)行了強(qiáng)制轉(zhuǎn)換。
四 類(lèi)型參數(shù)的限定
泛型的類(lèi)型擦除會(huì)把所有類(lèi)型參數(shù)當(dāng)做Object,但是我們也可以對(duì)參數(shù)類(lèi)型進(jìn)行上界限定。這樣類(lèi)型擦除就會(huì)轉(zhuǎn)換為限定類(lèi)型。
4.1 上界為某個(gè)具體類(lèi)或接口
public class Som<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
這樣使用Som類(lèi),類(lèi)型參數(shù)只接受Number及其子類(lèi)。
當(dāng)上界是泛型類(lèi)或者接口的時(shí)候,上界也需要類(lèi)型參數(shù)。如下:
public class Som<T extends Comparable<T>> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
4.2 上界為其他類(lèi)型參數(shù)
public class Som<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public <E extends T> void test(E e) {
System.out.println("Som test: e");
}
}
T是泛型類(lèi)Som的參數(shù)類(lèi)型,E的上界是T,也就是其它類(lèi)型參數(shù)。
五 泛型的通配符
泛型的通配符增強(qiáng)了方法的靈活性但也容易讓人困惑。Java中有無(wú)限定通配符<?>,上界限定通配符<? extends E>,下界限定通配符<? super E>這三種通配符。
5.1 無(wú)限定通配符<?>
需求:打印List中的元素。List是一個(gè)泛型類(lèi),有List<String>,List<Number>,List<Object>等可能。使用List<?>通配符,可以匹配任意List泛型。
代碼如下:
public static void printList(List<?> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
看起來(lái)很簡(jiǎn)單,但是此時(shí)的list是無(wú)法進(jìn)行add操作的,因?yàn)長(zhǎng)ist的類(lèi)型是未知的。這就是<?>的只讀性,稍后會(huì)有介紹。
5.2 有限通配符<? extends E>
同樣是一個(gè)打印List元素的例子,但是只接受類(lèi)型參數(shù)是Number及其子類(lèi)。
public static void printList(List<? extends Number> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
和<?>一樣,<? extends E>也具有只讀性。
5.3 <?>和<? extends E>的只讀性
通配符<?>和<? extends E>具有只讀性,即可以對(duì)其進(jìn)行讀取操作但是無(wú)法進(jìn)行寫(xiě)入。
public static void printList(List<?> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//一下操作不可以
list.add(1);
list.add("123");
}
原因在于:?就是表示類(lèi)型完全無(wú)知,? extends E表示是E的某個(gè)子類(lèi)型,但不知道具體子類(lèi)型,如果允許寫(xiě)入,Java就無(wú)法確保類(lèi)型安全性。假設(shè)我們?cè)试S寫(xiě)入,如果我們傳入的參數(shù)是List<Integer>,此時(shí)進(jìn)行add操作,可以添加任何類(lèi)型元素,就無(wú)法保證List<Integer>的類(lèi)型安全了。
5.4 超類(lèi)型<? super E>
超類(lèi)型通配符允許寫(xiě)入,例子如下:
public static void printList(List<? super String> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
list.add("123");
list.add("456");
}
這個(gè)很好理解,list的參數(shù)類(lèi)型是String的上界,必然可以添加String類(lèi)型的元素。
六 泛型與數(shù)組
Java不能創(chuàng)建泛型數(shù)組,以Som泛型類(lèi)為例,以下代碼編譯報(bào)錯(cuò):
Som<String> [] soms = new Som<String>[8];
原因是像Integer[]和Number[]之間有繼承關(guān)系,而List<Integer>和List<Number>沒(méi)有,如果允許泛型數(shù)組,那么編譯時(shí)無(wú)法發(fā)現(xiàn),運(yùn)行時(shí)也不是立即就能發(fā)現(xiàn)的問(wèn)題會(huì)出現(xiàn)。參看以下代碼:
Som<Integer>[] soms = new Som<Integer>[3];
Object[] objs = soms;
objs[0] = new Som<String>();
那我們?cè)趺创娣欧盒蛯?duì)象呢?可以使用原生數(shù)組或者泛型容器。