Java 泛型
泛型的本質是參數化類型。簡單解釋就是,將參數的數據類型也當作參數對待。泛型的目的就是為了寫一套代碼,可以到處通用,不用擔心類型安全的問題。泛型可以用在類、接口、方法中對應的就是泛型類、泛型接口和泛型方法。
一、為什麼要引入泛型?
我們先看一個例子:
獲取一個字符串對象,列印如下:
引入 Object 類型
這個類只能適用字符串類型,那要獲取整型等其他數據類型怎麼辦呢?於是我們想到了 Object 類型,它可以表示所有 Java 類類型數據,於是改造下:
列印結果,字符串類型和上面的列印結果是相同的:
讓方法返回一個 Object 類型數據,這樣似乎就解決了問題。但還是有個問題,因為方法返回的是 Object 類,返回值參與其它類型數據的計算時需要強制類型轉換。如:
使用泛型
Object 類型數據使用時需要事先知道參與計算的具體類型,有時候我們很難判斷需要什麼類型,如果寫錯了,那就會報錯。為了少寫代碼,且不出錯,於是就引入了泛型。通過泛型改造上面的類:
列印結果:
使用泛型後就不用強制轉換數據類型,代碼也變得通用了。
泛型的定義
使用泛型的方式有3種,分別是:
泛型類泛型方法泛型接口上面的代碼中用的是 T 來代指數據類型,也就是泛型參數。T 不是唯一的,可以用任意大寫字母替代,不過一般遵循如下原則:T:指一般的任何類。E:元素 Element 的意思,或者 Exception 異常的意思。K:鍵,Key 的意思。V:值,Value 的意思,通常與 K 一起配合使用。
泛型類,定義一個泛型類:
在類名後面需要用<>包裹,T 就是類型參數,多個類型參數,用不同的大寫字母且「,」分隔。創建泛型類:
<>中傳入對應的類型,類中的 T 就會被對應的類型取代。
泛型方法,定義一個泛型方法:
泛型方法的定義是在返回類型前面加<T>,返回類型也可以用泛型代指,如 T。方法中的類型化參數 T 不用<>。沒有返回類型的泛型方法:
泛型類和泛型方法可以同時使用,如:
泛型方法和普通方法的參數沒有關聯。
泛型接口
定義泛型接口:
泛型接口和泛型類的定義類似。
泛型通配符?
有時我們會碰到這樣的情況,一個夫類型的集合需要兼容子類,或者子類集合要兼容父類。如:
Integer繼承自Number 類,但不代表 List<Integer>繼承自 List<Number>,泛型也沒有繼承關係,所以不能像變量類型一樣兼容使用。但有時會又這類需求,於是引入?通配符:
<? extends T>上限通配符,指類型必須是 T 的子類,或 T 類型。
<? super T>下限通配符,指類型必須是 T 的父類,或 T 類型。 如:
還有一種是無限通配符:<?>本質是<? extends Object>,表示不知道什麼類型。
無限通配符可以方便使用但也是有限制的,無限通配符意味著不知道具體類型,所以數據只能讀取,不能寫入。如:
類型擦除
泛型只在編譯階段有效,編譯後類型會被擦除。如:
列印結果為 true,也即 l1 和 l2 的類型相同,都是java.util.ArrayList,泛型信息被擦除了。
泛型的限制
泛型不支持 8 中基本數據類型,需要用對應類型的包裝類。
List<int> l = new ArrayList<>(); // 編譯報錯
創建泛型類數組時只能創建無限定通配符的。
因為類型擦除後就不知道數組中具體存儲的哪種類型數據了。
泛型與反射
先看個例子:
創建一個接受Integer類型的泛型數組 list,如果往 list 中插入String類型數據時編譯出錯,類型限制。看下 List 的 add 方法:
E代表任意類型,所以類型擦除後 add 方法就變成了如下方式:
boolean add(Object obj);
通過反射可以獲取對象實例,並訪問對象數據。如:
因為泛型的類型擦除性質,利用反射就可以繞過編譯器的類型檢查,這也說明了反射會引入安全問題,需要謹慎使用。