一 泛型是什么
泛型最精準(zhǔn)的定義:參數(shù)化類型。具體點(diǎn)說就是處理的數(shù)據(jù)類型不是固定的洼滚,而是可以作為參數(shù)傳入埂息。定義泛型類、泛型接口遥巴、泛型方法千康,這樣,同一套代碼铲掐,可以用于多種數(shù)據(jù)類型拾弃。
二 泛型類和泛型方法
2.1 泛型類和接口
泛型類和接口類似,定義一個泛型類:
public class Som<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Som就是一個泛型類摆霉,value的類型是T豪椿,而T是參數(shù)化的。如果有多個類型參數(shù)携栋,使用分號隔開,如<U,V>搭盾。
使用泛型類:
Som<String> som = new Som<>();
som.setValue("Hi");
//som.setValue(123);編譯不通過
String str = som.getValue();
在使用中指定具體的類型實(shí)參。
2.2 泛型方法
定義一個泛型方法:
public static <V> V obtainV(V[] arr) {
return arr[arr.length / 2];
}
obtainV就是一個泛型方法婉支,返回值前有<V>鸯隅,可以處理任意類型數(shù)組。
使用泛型方法:
Integer [] arr = {1,2,3};
String [] arrStrs = {"1","2","3"};
int i = obtainV(arr);
String str = obtainV(arrStrs);
三 Java泛型的實(shí)現(xiàn)原理:類型擦除
泛型是JDK1.5引入的向挖,為了保持兼容蝌以,Java泛型的實(shí)現(xiàn)采用了類型擦除炕舵。類定義中的類型參數(shù)會被替換為Object,運(yùn)行時不知道泛型的實(shí)際類型參數(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();
可以看到在使用泛型的地方咽筋,編譯后生成的代碼,編譯器自動進(jìn)行了強(qiáng)制類型轉(zhuǎn)換碍彭。
Java的泛型實(shí)現(xiàn)就是如此:在編譯期進(jìn)行泛型檢查晤硕,編譯后的代碼擦除了類型信息悼潭,所有泛型都使用Object代替庇忌,并進(jìn)行了強(qiáng)制轉(zhuǎn)換。
四 類型參數(shù)的限定
泛型的類型擦除會把所有類型參數(shù)當(dāng)做Object舰褪,但是我們也可以對參數(shù)類型進(jìn)行上界限定皆疹。這樣類型擦除就會轉(zhuǎn)換為限定類型。
4.1 上界為某個具體類或接口
public class Som<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
這樣使用Som類占拍,類型參數(shù)只接受Number及其子類略就。
當(dāng)上界是泛型類或者接口的時候,上界也需要類型參數(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 上界為其他類型參數(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是泛型類Som的參數(shù)類型表牢,E的上界是T,也就是其它類型參數(shù)贝次。
五 泛型的通配符
泛型的通配符增強(qiáng)了方法的靈活性但也容易讓人困惑崔兴。Java中有無限定通配符<?>,上界限定通配符<? extends E>,下界限定通配符<? super E>這三種通配符。
5.1 無限定通配符<?>
需求:打印List中的元素蛔翅。List是一個泛型類敲茄,有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));
}
}
看起來很簡單,但是此時的list是無法進(jìn)行add操作的笋轨,因?yàn)長ist的類型是未知的秆剪。這就是<?>的只讀性,稍后會有介紹爵政。
5.2 有限通配符<? extends E>
同樣是一個打印List元素的例子鸟款,但是只接受類型參數(shù)是Number及其子類。
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>具有只讀性,即可以對其進(jìn)行讀取操作但是無法進(jìn)行寫入等龙。
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");
}
原因在于:?就是表示類型完全無知处渣,? extends E表示是E的某個子類型伶贰,但不知道具體子類型,如果允許寫入罐栈,Java就無法確保類型安全性黍衙。假設(shè)我們允許寫入,如果我們傳入的參數(shù)是List<Integer>荠诬,此時進(jìn)行add操作琅翻,可以添加任何類型元素,就無法保證List<Integer>的類型安全了柑贞。
5.4 超類型<? super E>
超類型通配符允許寫入方椎,例子如下:
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");
}
這個很好理解,list的參數(shù)類型是String的上界钧嘶,必然可以添加String類型的元素棠众。
六 泛型與數(shù)組
Java不能創(chuàng)建泛型數(shù)組,以Som泛型類為例有决,以下代碼編譯報(bào)錯:
Som<String> [] soms = new Som<String>[8];
原因是像Integer[]和Number[]之間有繼承關(guān)系闸拿,而List<Integer>和List<Number>沒有,如果允許泛型數(shù)組书幕,那么編譯時無法發(fā)現(xiàn)新荤,運(yùn)行時也不是立即就能發(fā)現(xiàn)的問題會出現(xiàn)。參看以下代碼:
Som<Integer>[] soms = new Som<Integer>[3];
Object[] objs = soms;
objs[0] = new Som<String>();
那我們怎么存放泛型對象呢台汇?可以使用原生數(shù)組或者泛型容器苛骨。