談談泛型和泛型擦除



所謂泛型,就是指在定義一個類、接口或者方法時可以指定類型參數。這個類型參數我們可以在使用類、接口或者方法時動態指定。

使用泛型可以給我們帶來如下的好處

  • 編譯時類型檢查:當我們使用泛型時,加入向容器中存入非特定對象在編譯階段就會報錯。假如不使用泛型,可以向容器中存入任意類型,容易出現類型轉換異常。
  • 不需要進行類型強制轉換:使用泛型后容器可以記住存入容器中的對象的類型;
  • 代碼可讀性提升:使用泛型后開發人員看一眼就知道容器中存放的是何種對象。

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

有了上面的泛型擦除知識后,我們就可以理解下面的現象了:

  1. 泛型類的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());  
}  
  1. 不能對泛型數組進行初始化
List<String>[] list = new List<String>[];  
  1. instanceof 不允許存在泛型參數
List<String> list = new ArrayList<String>();  
//在運行時list的泛型參數會被刪除,所以判斷不了類型
System.out.println(list instanceof List<String>)

2. 泛型的繼承

泛型類的繼承關系不是由泛型參數的繼承關系來決定的。比如說Box類是一個泛型類,現在給這個類賦兩個類型參數Integer和Number,雖然Integer是Number的子類,但是Box 和Box 沒有任何關系,他們之間唯一的關系就是他們都是Object的子類.

如果想要兩個泛型類之間具有繼承關系,那么我們需要定義這個兩個泛型類本身之間具有繼承關系,而不是泛型參數之間具有泛型關系。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 接口繼承了Collection 接口, 相信這樣的例子我們已經見的不少了:

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<?>,並且可以添加任意類型的元素。但其缺點在於不安全性、不便利性、不表述性(不應該使用原生類型的原因)。

List list1 = new ArrayList<Object>();
List list2 = new ArrayList<String>();

List只能接收泛型參數是Object的List實現類:

List<Object> list1 = new ArrayList<Object>(); 
List<Object> list2 = new ArrayList<String>(); //編譯報錯

7. List<Object>List<?>的區別

List,即通配符類型,其引用變量,同樣可以接受任何對應List 的參數化類型,包括List。但是由於?表示List中的元素是未知的,所以不能向其中添加任何元素,從中取出的元素是Object類型,要通過手動轉換才能得到原本的類型。List這種形式在定義泛型方法時比較常用:

//自定義泛型方法
public static void getData(List<?> data) 
//類型參數是Number或是其子類
public static void getUpperNumberData(List<? extends Number> data) 
public static void getUpperNumberData(List<? super Number> data)

List,即實際類型參數為Object的參數化類型,僅可以接受List和其本身類型。List已經明確確定List中的類型是Object或者其子類,從中取出元素時也不用強制轉換成Object。

8. List<Object>List<T>之間的區別

List 是Java中定義泛型方法的寫法,比如:

public <T> List<T> getList(T type) {
    List<T> list = new ArrayList<T>();
    list.add(type);
    return list;
}

在代碼運行之前不知道T是什么類型,只有在代碼被運行之后才可以確定T的類型。

List是一種具體的用法,表示這個List中只能放Objec類或者是Object的子類。

9. 泛型的協變逆變

java中泛型是不變的,既不能協變,也不能逆變。但是可以使用通配符實現類似的效果。個人覺得太糾結這些概念問題沒什么意思,這邊就只記錄下相關代碼。

List<? extends Food> foodList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>(); 
foodList = appleList; // ok 協變
List<? super Fruit> fruitList = new ArrayList<>();
List<Food> foodList = new ArrayList<>(); 
fruitList = foodList; // ok 逆變

公眾號推薦

歡迎大家關注我的微信公眾號「程序員自由之路」


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM