天天看點

Java 泛型和類型擦除

一、概念

  在 Java 語言處于還沒有出現泛型的版本時,隻能通過 Object 是所有類型的父類和類型強制轉換兩個特點的配合來實作類型泛化。這樣做有個缺點,就是隻有程式員和運作期的虛拟機才知道這個 Object 到底是個什麼類型的對象。在編譯期,編譯器無法檢查這個 Object 的強制轉換是否成功。是以,許多 ClassCastException 的風險就會轉移到程式運作期之中。  

  泛型是 JDK 1.5 的一項新增特性,它的本質是參數化類型(Parametersized Type),就比如我們定義方法的時候,定義一個變量,稱為形參,變量值根據傳進去的實參的值不同而改變。而泛型的出現,就是為了解決類型也能根據傳進去的類型改變的問題,也就是說所操作的資料類型被指定為一個參數。主要用在定義類、接口、方法的建立上,可以很好的減少代碼的重複。

  然而美中不足的是,Java 中的泛型實作并不是真正的泛型,它隻在程式源碼中存在,在編譯後的位元組碼檔案中,就已經替換為原來的原生類型了,并在相應的地方插入了強制轉換代碼,是以對于運作期的 Java 語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類,這種實作方法被稱為泛型的類型擦除,是以泛型技術實際上是 Java 語言的一顆文法糖。

tips:文法糖雖然不會提供實際性的功能改進,但是他們或能提高工作效率,或能提升文法的嚴謹性,或能減少編碼出錯的機會。常見的文法糖還有“内部類”、“自動裝箱/拆箱”、“斷言語句”、“枚舉類”等等

二、泛型使用

1. 泛型中的辨別符含義

 E - Element (在集合中使用,因為集合中存放的是元素)

 T - Type(Java 類)

 K - Key(鍵)

 V - Value(值)

 N - Number(數值類型)

? -  表示不确定的java類型

 S、U、V  - 2nd、3rd、4th types

2、定義一個泛型方法

  首先,泛型的聲明,必須在方法的修飾符(public, static, final, abstract 等)之後,傳回值聲明之前,可以聲明多個泛型,用逗号隔開。

public static <T1, T2> T1 print(List<T1> list, List<T2> list2) {
        return list.get(0);
    }      

3、定義一個泛型類

@Data
public class Box<T> {//這裡可以定義多個泛型,用逗号分割

    private String name;
    private T t;
    
    /**
     * 泛型繼承
     */
    public static class CircleBox extends Box<String> {

    }

    public static class SquareBox extends Box<Integer> {
        
    }
}      

三、泛型擦除

  接下來我們來看一個泛型擦除的例子

public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("hello", "hello");
        map.put("world", "world");
        System.out.println(map.get("hello"));
        System.out.println(map.get("world"));
    }      

  把這段 Java 代碼編譯成 Class 檔案,然後再用位元組碼反編譯工具進行反編譯後,會發現所有泛型都不見了,泛型類型都變回了原生類型,隻是在相應的地方做了類型強制轉換。

public static void main(String[] args) {
      HashMap map = new HashMap();
      map.put("hello", "hello");
      map.put("world", "world");
      System.out.println((String)map.get("hello"));
      System.out.println((String)map.get("world"));
   }      

四、泛型識别

  按照我們上面的說法,泛型在編譯期間就被擦除了,那麼在代碼運作期各種場景(如反射等)下該如何識别參數化類型呢?總不能識别出來都是 Object 吧?這就要說到一個虛拟機屬性 —— Signature,它的作用就是存儲一個方法在位元組碼層面的特征簽名,這個屬性儲存的參數類型并不是原生類型,而是包括了參數化類型的資訊。

  是以,擦除法所謂的擦除,僅僅是對方法的 Code 屬性中的位元組碼進行擦除,實際上中繼資料中還是保留了泛型資訊,這也是我們能通過反射等手段取得參數化類型的根本依據。