Java泛型詳解,史上最全圖文詳解!
- 2022 年 2 月 15 日
- 筆記
- JAVA, 大廠架構面試乾貨合集
泛型在java中有很重要的地位,無論是開源框架還是JDK源碼都能看到它。
毫不誇張的說,泛型是通用設計上必不可少的元素,所以真正理解與正確使用泛型,是一門必修課。
一:泛型本質
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數化類型,即給類型指定一個參數,然後在使用時再指定此參數具體的值,那樣這個類型就可以在使用時決定了。這種參數類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。
二:為什麼使用泛型
泛型的好處是在編譯的時候檢查類型安全,並且所有的強制轉換都是自動和隱式的,提高代碼的重用率。
(1)保證了類型的安全性。
在沒有泛型之前,從集合中讀取到的每一個對象都必須進行類型轉換,如果不小心插入了錯誤的類型對象,在運行時的轉換處理就會出錯。
比如:沒有泛型的情況下使用集合:
public static void noGeneric() {
ArrayList names = new ArrayList();
names.add("mikechen的互聯網架構");
names.add(123); //編譯正常
}
有泛型的情況下使用集合:
public static void useGeneric() {
ArrayList<String> names = new ArrayList<>();
names.add("mikechen的互聯網架構");
names.add(123); //編譯不通過
}
有了泛型後,定義好的集合names在編譯的時候add(123)就會編譯不通過。
相當於告訴編譯器每個集合接收的對象類型是什麼,編譯器在編譯期就會做類型檢查,告知是否插入了錯誤類型的對象,使得程序更加安全,增強了程序的健壯性。
(2) 消除強制轉換
泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換,這使得代碼更加可讀,並且減少了出錯機會。
還是舉例說明,以下沒有泛型的代碼段需要強制轉換:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
當重寫為使用泛型時,代碼不需要強制轉換:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
(3)避免了不必要的裝箱、拆箱操作,提高程序的性能
在非泛型編程中,將筒單類型作為Object傳遞時會引起Boxing(裝箱)和Unboxing(拆箱)操作,這兩個過程都是具有很大開銷的。引入泛型後,就不必進行Boxing和Unboxing操作了,所以運行效率相對較高,特別在對集合操作非常頻繁的系統中,這個特點帶來的性能提升更加明顯。
泛型變量固定了類型,使用的時候就已經知道是值類型還是引用類型,避免了不必要的裝箱、拆箱操作。
object a=1;//由於是object類型,會自動進行裝箱操作。
int b=(int)a;//強制轉換,拆箱操作。這樣一去一來,當次數多了以後會影響程序的運行效率。
使用泛型之後
public static T GetValue<T>(T a)
{
return a;
}
public static void Main()
{
int b=GetValue<int>(1);//使用這個方法的時候已經指定了類型是int,所以不會有裝箱和拆箱的操作。
}
(4)提高了代碼的重用性。
三:如何使用泛型
泛型有三種使用方式,分別為:泛型類、泛型接口和泛型方法。
1、泛型類
泛型類:把泛型定義在類上
定義格式:
public class 類名 <泛型類型1,...> {
}
注意事項:泛型類型必須是引用類型(非基本數據類型)
定義泛型類,在類名後添加一對尖括號,並在尖括號中填寫類型參數,參數可以有多個,多個參數使用逗號分隔:
public class GenericClass<ab,a,c> {}
當然,這個後面的參數類型也是有規範的,不能像上面一樣隨意,通常類型參數我們都使用大寫的單個字母表示:
T:任意類型 type
E:集合中元素的類型 element
K:key-value形式 key
V: key-value形式 value
示例代碼:
泛型類:
public class GenericClass<T> {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
測試類:
//TODO 1:泛型類
GenericClass<String> name = new GenericClass<>("mikechen的互聯網架構");
System.out.println(name.getValue());
GenericClass<Integer> number = new GenericClass<>(123);
System.out.println(number.getValue());
運行結果:
2、泛型接口
泛型方法概述:把泛型定義在方法上
定義格式:
public <泛型類型> 返回類型 方法名(泛型類型 變量名) {
}
注意要點:
-
- 方法聲明中定義的形參只能在該方法里使用,而接口、類聲明中定義的類型形參則可以在整個接口、類中使用。當調用fun()方法時,根據傳入的實際對象,編譯器就會判斷出類型形參T所代表的實際類型。
public interface GenericInterface<T> {
void show(T value);}
}
public class StringShowImpl implements GenericInterface<String> {
@Override
public void show(String value) {
System.out.println(value);
}}
public class NumberShowImpl implements GenericInterface<Integer> {
@Override
public void show(Integer value) {
System.out.println(value);
}}
注意:使用泛型的時候,前後定義的泛型類型必須保持一致,否則會出現編譯異常:
GenericInterface<String> genericInterface = new NumberShowImpl();//編譯異常
或者乾脆不指定類型,那麼 new 什麼類型都是可以的:
GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();
3、泛型方法
泛型方法,是在調用方法的時候指明泛型的具體類型 。
- 定義格式:
修飾符 <代表泛型的變量> 返回值類型 方法名(參數){ }
例如:
/**
*
* @param t 傳入泛型的參數
* @param <T> 泛型的類型
* @return T 返回值為T類型
* 說明:
* 1)public 與 返回值中間<T>非常重要,可以理解為聲明此方法為泛型方法。
* 2)只有聲明了<T>的方法才是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法。
* 3)<T>表明該方法將使用泛型類型T,此時才可以在方法中使用泛型類型T。
* 4)與泛型類的定義一樣,此處T可以隨便寫為任意標識,常見的如T、E等形式的參數常用於表示泛型。
*/
public <T> T genercMethod(T t){
System.out.println(t.getClass());
System.out.println(t);
return t;
}
public static void main(String[] args) {
GenericsClassDemo<String> genericString = new GenericsClassDemo("helloGeneric"); //這裡的泛型跟下面調用的泛型方法可以不一樣。
String str = genericString.genercMethod("hello");//傳入的是String類型,返回的也是String類型
Integer i = genericString.genercMethod(123);//傳入的是Integer類型,返回的也是Integer類型
}
class java.lang.String
hello
class java.lang.Integer
123
這裡可以看出,泛型方法隨着我們的傳入參數類型不同,他得到的類型也不同。泛型方法能使方法獨立於類而產生變化。
四:泛型通配符
Java泛型的通配符是用於解決泛型之間引用傳遞問題的特殊語法, 主要有以下三類:
//表示類型參數可以是任何類型
public class Apple<?>{}
//表示類型參數必須是A或者是A的子類
public class Apple<T extends A>{}
//表示類型參數必須是A或者是A的超類型
public class Apple<T supers A>{}
1. 無邊界的通配符(Unbounded Wildcards), 就是, 比如List
無邊界的通配符的主要作用就是讓泛型能夠接受未知類型的數據.
2. 固定上邊界的通配符(Upper Bounded Wildcards),採用<? extends E>的形式
使用固定上邊界的通配符的泛型, 就能夠接受指定類及其子類類型的數據。
要聲明使用該類通配符, 採用<? extends E>的形式, 這裡的E就是該泛型的上邊界。
注意: 這裡雖然用的是extends關鍵字, 卻不僅限於繼承了父類E的子類, 也可以代指顯現了接口E的類
3. 固定下邊界的通配符(Lower Bounded Wildcards),採用<? super E>的形式
使用固定下邊界的通配符的泛型, 就能夠接受指定類及其父類類型的數據.。
要聲明使用該類通配符, 採用<? super E>的形式, 這裡的E就是該泛型的下邊界.。
注意: 你可以為一個泛型指定上邊界或下邊界, 但是不能同時指定上下邊界。
五:泛型中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類型
六:泛型的實現原理
泛型本質是將數據類型參數化,它通過擦除的方式來實現,即編譯器會在編譯期間「擦除」泛型語法並相應的做出一些類型轉換動作。
看一個例子就應該清楚了,例如:
public class Caculate<T> {
private T num;
}
我們定義了一個泛型類,定義了一個屬性成員,該成員的類型是一個泛型類型,這個 T 具體是什麼類型,我們也不知道,它只是用於限定類型的。
反編譯一下這個 Caculate 類:
public class Caculate{
public Caculate(){}
private Object num;
}
發現編譯器擦除 Caculate 類後面的兩個尖括號,並且將 num 的類型定義為 Object 類型。
那麼是不是所有的泛型類型都以 Object 進行擦除呢?大部分情況下,泛型類型都會以 Object 進行替換,而有一種情況則不是。那就是使用到了extends和super語法的有界類型,如:
public class Caculate<T extends String> {
private T num;
}
這種情況的泛型類型,num 會被替換為 String 而不再是 Object。
這是一個類型限定的語法,它限定 T 是 String 或者 String 的子類,也就是你構建 Caculate 實例的時候只能限定 T 為 String 或者 String 的子類,所以無論你限定 T 為什麼類型,String 都是父類,不會出現類型不匹配的問題,於是可以使用 String 進行類型擦除。
實際上編譯器會正常的將使用泛型的地方編譯並進行類型擦除,然後返回實例。但是除此之外的是,如果構建泛型實例時使用了泛型語法,那麼編譯器將標記該實例並關注該實例後續所有方法的調用,每次調用前都進行安全檢查,非指定類型的方法都不能調用成功。
實際上編譯器不僅關注一個泛型方法的調用,它還會為某些返回值為限定的泛型類型的方法進行強制類型轉換,由於類型擦除,返回值為泛型類型的方法都會擦除成 Object 類型,當這些方法被調用後,編譯器會額外插入一行 checkcast 指令用於強制類型轉換,這一個過程就叫做『泛型翻譯』。
七:最後
以上我就分別從Java泛型的誕生,再到泛型的使用,以及泛型的實現原理等六個方面進行了完整詳解,希望對你有所用!
關於作者:mikechen,十餘年BAT架構經驗,資深技術專家,曾任職阿里、淘寶、百度。
關注個人公眾號:mikechen的互聯網架構,十餘年BAT架構經驗傾囊相授!
在公眾號菜單欄對話框回復【架構】關鍵詞,即可查看我原創的300期+BAT架構技術系列文章與1000+大廠面試題答案合集。