所謂泛型,就是指在定義一個類、接口或者方法時可以指定類型參數。這個類型參數我們可以在使用類、接口或者方法時動態指定。
使用泛型可以給我們帶來如下的好處:
- 編譯時類型檢查:當我們使用泛型時,加入向容器中存入非特定對象在編譯階段就會報錯。假如不使用泛型,可以向容器中存入任意類型,容易出現類型轉換異常。
- 不需要進行類型強制轉換:使用泛型后容器可以記住存入容器中的對象的類型;
- 代碼可讀性提升:使用泛型后開發人員看一眼就知道容器中存放的是何種對象。
PS:本文參考了Java中泛型 類型擦除和關於List、List、List的區別,讀者也可以點擊查看原文的全部內容。
1. 泛型擦除
泛型擦除是指Java中的泛型只在編譯期有效,在運行期間會被刪除。也就是說所有泛型參數在編譯后都會被清除掉。
public class Foo {
public void listMethod(List<String> stringList){
}
public void listMethod(List<Integer> intList) {
}
}
上面這段代碼編譯時會報方法重載錯誤,原因是上面兩個方法的參數是泛型參數,在編譯后會被泛型擦除,最后兩個方法都會是 public void listMethod(List intList),所以會報重載錯誤的。
在編譯器編譯后,泛型的轉換規則如下:
- List
、List 擦除后的類型為 List; - List
[]、List [] 擦除后的類型為 List[]; - List<? extends E>、List<? super E> 擦除后的類型為 List
; - List<T extends Serialzable & Cloneable> 擦除后類型為 List
。
有了上面的泛型擦除知識后,我們就可以理解下面的現象了:
- 泛型類的class對象相同
public static void main(String[] args) {
List<String> ls = new ArrayList<String>();
List<Integer> li = new ArrayList<Integer>();
System.out.println(ls.getClass() == li.getClass());
}
- 不能對泛型數組進行初始化
List<String>[] list = new List<String>[];
- instanceof 不允許存在泛型參數
List<String> list = new ArrayList<String>();
//在運行時list的泛型參數會被刪除,所以判斷不了類型
System.out.println(list instanceof List<String>)
2. 泛型的繼承
泛型類的繼承關系不是由泛型參數的繼承關系來決定的。比如說Box類是一個泛型類,現在給這個類賦兩個類型參數Integer和Number,雖然Integer是Number的子類,但是Box
如果想要兩個泛型類之間具有繼承關系,那么我們需要定義這個兩個泛型類本身之間具有繼承關系,而不是泛型參數之間具有泛型關系。JDK中有很多這種列子,我們可以拿List和ArrayList做一個列子。
public interface List<E> extends Collection<E> {
...
}
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
}
這時, 我們就可以說ArrayList
List<String> list = new ArrayList<String>();
但是需要注意的是,上面的賦值中泛型參數必須是一致的,下面的賦值形式編譯期將會報編譯錯誤。
List<Map> list = new ArrayList<HashMap>();
3. 泛型通配符使用
泛型統配符一般使用在泛型類的定義和泛型方法的定義。也就是說泛型通配符主要使用在類和方法的定義中。很少在具體的使用中用到(List<?>這種情況使用的很少),所以關於泛型統配符我們重點掌握使用通配符進行泛型的定義就好了。
//表示類型參數可以是任何類型
public class Apple<?>{}
//表示類型參數必須是A或者是A的子類
public class Apple<T extends A>{}
//表示類型參數必須是A或者是A的超類型
public class Apple<T supers A>{}
//定義泛型方法,表示參數必須是ApplicationContext的子類
public static <T extends ApplicationContext> List<T> getList(T type)
4. 泛型中KTVE的含義
如果點開JDK中一些泛型類的源碼,我們會看到下面這些代碼:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
...
}
上面這些泛型類定義中的泛型參數E、K和V都是什么意思呢?其實這些參數名稱是可以任意指定,就想方法的參數名一樣可以任意指定,但是我們通常會起一個有意義的名稱,讓別人一看就知道是什么意思。泛型參數也一樣,E一般是指元素,用來集合類中。常見泛型參數名稱有如下:
- E: Element (在集合中使用,因為集合中存放的是元素)
- T:Type(Java 類)
- K: Key(鍵)
- V: Value(值)
- N: Number(數值類型)
- ?: 表示不確定的java類型
- S、U、V:2nd、3rd、4th types
5. 泛型中Object的含義
6. List<Object>
和List的區別
List是原始類型,其引用變量可以接受任何對應List
List list1 = new ArrayList<Object>();
List list2 = new ArrayList<String>();
List