泛型是什麼
- 泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理 解呢?
- 顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形 參),然後在使用/調用時傳入具體的類型(類型實參)。
- 泛型的本質是爲了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說 在泛型使用過程中,操作的數據類型被指定爲一個參數,這種參數類型可以用在類、接口和方法中,分別被稱爲泛型類、泛型接口、 泛型方法。
- 引入一個類型變量 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
* 引入一個類型變量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());
}
- 傳入泛型實參
/**
* 實現泛型類,方式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 屬性中的字節碼進行擦除,實際 上元數據中還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。