Java中的泛型和泛型通配符詳解
1. 前言
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機(jī)制,該機(jī)制允許開發(fā)者在編譯時檢測到非法的類型。
泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)。
2. 泛型的作用
那么泛型的作用就是在編譯的時候能夠檢查類型安全,并且所有的強制轉(zhuǎn)換都是自動和隱式的。 在沒有泛型的情況的下,通過對類型 Object 的引用來實現(xiàn)參數(shù)的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉(zhuǎn)換,而這種轉(zhuǎn)換是要求開發(fā)者對實際參數(shù)類型可以預(yù)知的情況下進(jìn)行的。對于強制類型轉(zhuǎn)換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現(xiàn)異常,這是本身就是一個安全隱患。
public class GlmapperGeneric<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } /** * 不指定類型 */ public void noSpecifyType() { GlmapperGeneric glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test"); // 需要強制類型轉(zhuǎn)換 String test = (String) glmapperGeneric.get(); System.out.println(test); } /** * 指定類型 */ public void specifyType() { GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test"); // 不需要強制類型轉(zhuǎn)換 String test = glmapperGeneric.get(); System.out.println(test); } }
上面這段代碼中的 specifyType 方法中 省去了強制轉(zhuǎn)換,可以在編譯時候檢查類型安全,可以用在類,方法,接口上。
3. 泛型通配符
在查看源碼時,能發(fā)現(xiàn)有各種沒有見過的泛型通配符,例如:T、K、V、E、?等等,那這些通配符究竟有什么意義呢?
3.1 常用的K、V、T、E、?
本質(zhì)上這些通配符沒有任何區(qū)別,只是程序員在編碼過程中的一些約定俗成的規(guī)范。比如上述代碼中的 T ,我們可以換成 A-Z 之間的任何一個 字母都可以,并不會影響程序的正常運行,但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些。通常情況下,T,E,K,V,?是這樣約定的:
- ? 表示不確定的 java 類型
- T (type) 表示具體的一個java類型
- K V (key value) 分別代表java鍵值中的Key Value
- E (element) 代表Element
3.2 無界通配符 “?”
可以指定任意的類型,沒有任何限制作用。
例如:
//測試泛型的定義 public class Generic<T> { private T name; private T flag; public void setFlag(T flag){ this.flag = flag; } public T getFlag(){ return this.flag; } } public class ShowMsg { /*如果在Generic對象中確定了類型,那么調(diào)用 例如定義的對象為Generic<String> g時,只 能輸出String類型的getFlag(),而Generic<?> 則表示通配任何類型*/ public void showFlag(Generic<?> g){ System.out.println(g.getFlag()); } } //測試無界通配符 public class Test06 { public static void main(String[] args) { ShowMsg s = new ShowMsg(); Generic<Integer> c = new Generic<>(); c.setFlag(100); s.showFlag(c); Generic<Number> c1 = new Generic<>(); c1.setFlag(20); s.showFlag(c1); Generic<String> c2 = new Generic<>(); c2.setFlag("oldlu"); s.showFlag(c2); } }
3.3 上屆通配符 <? extend E>
特征: 用 extend 關(guān)鍵字聲明,表示參數(shù)化的類型可能是所指定的類型,或者是此類型的子類。
好處:
- 如果傳入的類型不是 E 或者 E 的子類,編譯不成功
- 泛型中可以使用 E 的方法,要不然還得強轉(zhuǎn)成 E 才能使用
舉例:
3.4 下屆通配符 <? supper E>
特征: 用 supper 關(guān)鍵字聲明,表示參數(shù)化的類型可能是所指定的類型,或者是此類型的父類型,直至 Object。
注意:在類型參數(shù)中使用 super 表示這個泛型中的參數(shù)必須是 E 或者 E 的父類。
舉例:
泛型限制為 貓科動物 或者 貓科動物的父類,所以 貓 作為子類,是不能傳入的。
注意: 上界通配符主要用于讀數(shù)據(jù),下界通配符主要用于寫數(shù)據(jù)。
3.5 ?和 T 的區(qū)別
?和 T 都表示不確定的類型,區(qū)別在于我們可以對 T 進(jìn)行操作,但是對 ?不行,比如如下這種 :
// 可以 T t = operate(); // 不可以 ?car = operate();
區(qū)別1:可以通過 T 保證參數(shù)的一致性
泛型方法的定義:
interface MyGeneric { // 通過 T 來 確保 泛型參數(shù)的一致性 <T> void testT(List<T> dest, List<T> src); //通配符是 不確定的,所以這個方法不能保證兩個 List 具有相同的元素類型 void testNon(List<?> dest, List<?> src); } class GlmapperGeneric<E> implements MyGeneric { @Override public <T> void testT(List<T> dest, List<T> src) {} @Override public void testNon(List<?> dest, List<?> src) {} } @Test public void test() { GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric<>(); List<String> dest = new ArrayList<>(); List<Number> src = new ArrayList<>(); // 不報錯,”?“ 忽略參數(shù)是否一致,只要傳入即可。 glmapperGeneric.testNon(dest, src); // 報錯,“T” 會校驗參數(shù)是否一致。 glmapperGeneric.testT(dest, src); }
區(qū)別2:類型參數(shù)可以多重限定而通配符不行
interface MultiLimitInterfaceA {} interface MultiLimitInterfaceB {} class MultiLimit implements MultiLimitInterfaceA, MultiLimitInterfaceB { /** * 使用 & 符,設(shè)置多重邊界 */ public <T extends MultiLimitInterfaceA & MultiLimitInterfaceB> void method(T t) { } }
使用 & 符號設(shè)定多重邊界(Multi Bounds),指定泛型類型 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子類型,此時變量 t 就具有了所有限定的方法和屬性。
對于通配符 “?” 來說,因為它不是一個確定的類型,所以不能進(jìn)行多重限定。
區(qū)別3:通配符可以使用超類限定而類型參數(shù)不行
例如:通配符 ? 可以
List<? extends T> getList(); List<? super T> getList();
但參數(shù) T 只能:
List<T extends A> getList();
4. Class<T> 和 Class<?> 的區(qū)別
通過動態(tài)代理獲取實例的例子演示T和?的區(qū)別:
/** * Class<T> 表明是一個確定的Java類型 * Class<?> 是一個不確定的Java類型 * 如果使用Class<?>還需要強制類型轉(zhuǎn)換,所以此處必須使用<T> */ public <T> T createInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException { return clazz.newInstance(); } @Test public void test() throws InstantiationException, IllegalAccessException { A instance = createInstance(A.class); B instance1 = createInstance(B.class); } class A {} class B {}
總結(jié):Class<T>
在實例化時,需要將T替換成具體類。Class<?>
是通配泛型,? 可以代表任何類型,所以主要在聲明時的限制。 例如:
// 不報錯 public Class<?> clazz; // 報錯 public Class<T> clazz;
如果想Class<T>
在聲明時不報錯,則當(dāng)前類也指定泛型 T。
class Test<T> { public Class<?> clazz; // 不報錯 public Class<T> clazz; }
到此這篇關(guān)于Java中的泛型和泛型通配符詳解的文章就介紹到這了,更多相關(guān)Java泛型通配符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA WSIMPORT生成WEBSERVICE客戶端401認(rèn)證過程圖解
這篇文章主要介紹了JAVA WSIMPORT生成WEBSERVICE客戶端401認(rèn)證過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10Java 使用IO流實現(xiàn)大文件的分割與合并實例詳解
這篇文章主要介紹了Java 使用IO流實現(xiàn)大文件的分割與合并實例詳解的相關(guān)資料,需要的朋友可以參考下2016-12-12詳解Java是如何通過接口來創(chuàng)建代理并進(jìn)行http請求
今天給大家?guī)淼闹R是關(guān)于Java的,文章圍繞Java是如何通過接口來創(chuàng)建代理并進(jìn)行http請求展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06springboot使用maven實現(xiàn)多環(huán)境運行和打包問題
這篇文章主要介紹了springboot使用maven實現(xiàn)多環(huán)境運行和打包問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07Java中的NoClassDefFoundError報錯含義解析
這篇文章主要為大家介紹了Java中的NoClassDefFoundError含義詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-11-11