Java中的泛型以及JVM是如何實現泛型的?

泛型是什麼

  • 泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理 解呢?
  • 顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形 參),然後在使用/調用時傳入具體的類型(類型實參)。
  • 泛型的本質是爲了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說 在泛型使用過程中,操作的數據類型被指定爲一個參數,這種參數類型可以用在類、接口和方法中,分別被稱爲泛型類、泛型接口、 泛型方法。
  • 引入一個類型變量 T(其他大寫字母都可以,不過常用的就是 T,E,K,V 等等),並且用<>括起來,並放在類名的後面。泛型類 是允許有多個類型變量的。

泛型類

/**
 * 泛型類
 * 引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等)
 */
public class NormalGeneric<T> {
    private T data;

    public NormalGeneric() {
    }

    public NormalGeneric(T data) {
        this();
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric<String> normalGeneric = new NormalGeneric<>();
        normalGeneric.setData("Test");
        System.out.println(normalGeneric.getData());
    }
}

泛型接口

  • 泛型接口與泛型類的定義基本相同
/**
 *泛型接口
 * 引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等)
 */
public interface Generator<T> {
    public T next();
}
  • 而實現泛型接口的類,有兩種實現方法:
  1. 未傳入泛型實參時:
/**
 * 實現泛型類,方式1
 * 引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等)
 */
public class ImplGenerator<T> implements Generator<T> {

    private T data;

    public ImplGenerator(T data) {
        this.data = data;
    }

    @Override
    public T next() {
        return data;
    }
}

在 new 出類的實例時,需要指定具體類型:

 public static void main(String[] args) {
        ImplGenerator<String> implGenerator = new ImplGenerator<>("Test");
        System.out.println(implGenerator.next());

    }
  1. 傳入泛型實參
/**
 *  實現泛型類,方式2
 */
public class ImplGenerator2 implements Generator<String> {
    @Override
    public String next() {
        return "King";
    }
}

在 new 出類的實例時,和普通的類沒區別。

 public static void main(String[] args) {
        ImplGenerator2 implGenerator2 = new ImplGenerator2();
        System.out.println(implGenerator2.next());
    }

泛型方法

/**
 * 泛型方法
 * 引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等)
 * 說明:
 * 1)public 與 返回值中間<T>非常重要,可以理解爲聲明此方法爲泛型方法。
 * 2)只有聲明瞭<T>的方法纔是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法。
 * 3)<T>表明該方法將使用泛型類型T,此時纔可以在方法中使用泛型類型T。
 * 4)與泛型類的定義一樣,此處T可以隨便寫爲任意標識,常見的如T、E、K、V等形式的參數常用於表示泛型。
 */
public class GenericMethod {
    //泛型方法
    public <T> T genericMethod(T t) {
        return t;
    }

    //普通方法
    public void test(int x, int y) {
        System.out.println(x + y);
    }

    public static void main(String[] args) {
        GenericMethod genericMethod = new GenericMethod();
        genericMethod.test(13, 7);
        System.out.println(genericMethod.<String>genericMethod("Test"));
        System.out.println(genericMethod.genericMethod(180));
    }
}

泛型方法,是在調用方法的時候指明泛型的具體類型 ,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類

爲什麼我們需要泛型?

通過兩段代碼我們就可以知道爲何我們需要泛型

/**
 * 爲什麼需要泛型
 */
public class NeedGeneric {

    public int addInt(int x,int y){
        return x+y;
    }

    public float addFloat(float x,float y){
        return x+y;
    }

    public static void main(String[] args) {
        //不使用泛型
        NeedGeneric needGeneric = new NeedGeneric();
        System.out.println(needGeneric.addInt(1,2));
        System.out.println(needGeneric.addFloat(1.2f,2.4f));

        //使用泛型
        System.out.println(needGeneric.add(3.2d,4.5d));
        System.out.println(needGeneric.add(1,2));
    }
    //泛型方法
    public <T extends Number> double add(T x,T y){
        return x.doubleValue()+y.doubleValue();
    }
}

實際開發中,經常有數值類型求和的需求,例如實現 int 類型的加法, 有時候還需要實現 long 類型的求和, 如果還需要 double 類型 的求和,需要重新在重載一個輸入是 double 類型的 add 方法。

所以泛型的好處就是:

  • 適用於多種數據類型執行相同的代碼
  • 泛型中的類型在使用時指定,不需要強制類型轉換

虛擬機是如何實現泛型的?

  • Java 語言中的泛型,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型(RawType,也稱爲裸類 型)了,並且在相應的地方插入了強制轉型代碼,因此,對於運行期的 Java 語言來說,ArrayList<int>與 ArrayList<String>就是同一 個類,所以泛型技術實際上是 Java 語言的一顆語法糖,Java 語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型稱爲僞泛 型。

  • 將一段 Java 代碼編譯成 Class 文件,然後再用字節碼反編譯工具進行反編譯後,將會發現泛型都不見了,程序又變回了 Java 泛型 出現之前的寫法,因爲泛型類型都變回了原生類型

/**
 * 泛型擦除
 */
public class Theory {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("King","18");
        System.out.println(map.get("King"));
    }
}

在這裏插入圖片描述

使用泛型注意事項(作爲了解)

在這裏插入圖片描述
上面這段代碼是不能被編譯的,因爲參數 List<Integer>和 List<String>編譯之後都被擦除了,變成了一樣的原生類型 List<E>, 擦除動作導致這兩種方法的特徵簽名變得一模一樣(注意在 IDEA 中是不行的,但是 jdk 的編譯器是可以,因爲 jdk 是根據方法返回值+ 方法名+參數)。

  • JVM 版本兼容性問題:JDK1.5 以前,爲了確保泛型的兼容性,JVM 除了擦除,其實還是保留了泛型信息(Signature 是其中最重要的 一項屬性,它的作用就是存儲一個方法在字節碼層面的特徵簽名,這個屬性中保存的參數類型並不是原生類型,而是包括了參數化類 型的信息)----弱記憶
  • 另外,從 Signature 屬性的出現我們還可以得出結論,擦除法所謂的擦除,僅僅是對方法的 Code 屬性中的字節碼進行擦除,實際 上元數據中還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章