詳解Java泛型及其應(yīng)用
引出泛型
我們通過(guò)如下的示例,引出為什么泛型的概念。
public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add("abc"); list.add(2); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // error System.out.println("name:" + name); } } }
當(dāng)獲取列表中的第二個(gè)元素時(shí),會(huì)報(bào)錯(cuò),java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。這是常見(jiàn)的類(lèi)型轉(zhuǎn)換錯(cuò)誤。
當(dāng)我們將元素放入到列表中,并沒(méi)有使用指定的類(lèi)型,在取出元素時(shí)使用的是默認(rèn)的 Object 類(lèi)型。因此很容易出現(xiàn)類(lèi)型轉(zhuǎn)換的異常。
我們想要實(shí)現(xiàn)的結(jié)果是,集合能夠記住集合內(nèi)元素各類(lèi)型,且能夠達(dá)到只要編譯時(shí)不出現(xiàn)問(wèn)題,運(yùn)行時(shí)就不會(huì)出現(xiàn) java.lang.ClassCastException 異常。泛型剛好能滿足我們的需求。
什么是泛型?
泛型,即參數(shù)化類(lèi)型。一提到參數(shù),最熟悉的就是定義方法時(shí)有形參,然后調(diào)用此方法時(shí)傳遞實(shí)參。那么參數(shù)化類(lèi)型怎么理解呢?顧名思義,就是將類(lèi)型由原來(lái)的具體的類(lèi)型參數(shù)化,類(lèi)似于方法中的變量參數(shù),此時(shí)類(lèi)型也定義成參數(shù)形式(可以稱之為類(lèi)型形參),然后在使用/調(diào)用時(shí)傳入具體的類(lèi)型(類(lèi)型實(shí)參)。
泛型的本質(zhì)是為了參數(shù)化類(lèi)型,即在不創(chuàng)建新的類(lèi)型的情況下,通過(guò)泛型指定的不同類(lèi)型來(lái)控制形參具體限制的類(lèi)型。在泛型使用過(guò)程中,操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù),這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法中,分別被稱為泛型類(lèi)、泛型接口和泛型方法。
泛型的特點(diǎn)
Java 語(yǔ)言中引入泛型是一個(gè)較大的功能增強(qiáng)。不僅語(yǔ)言、類(lèi)型系統(tǒng)和編譯器有了較大的變化,已支持泛型,而且類(lèi)庫(kù)也進(jìn)行了大翻修,所以許多重要的類(lèi),比如集合框架,都已經(jīng)成為泛型化的了。這帶來(lái)了很多好處:
- 類(lèi)型安全。 泛型的主要目標(biāo)是提高 Java 程序的類(lèi)型安全。通過(guò)知道使用泛型定義的變量的類(lèi)型限制,編譯器可以在一個(gè)高得多的程度上驗(yàn)證類(lèi)型假設(shè)。
- 消除強(qiáng)制類(lèi)型轉(zhuǎn)換。 泛型的一個(gè)附帶好處是,消除源代碼中的許多強(qiáng)制類(lèi)型轉(zhuǎn)換。這使得代碼更加可讀,并且減少了出錯(cuò)機(jī)會(huì)。
- 潛在的性能收益。 泛型為較大的優(yōu)化帶來(lái)可能。在泛型的初始實(shí)現(xiàn)中,編譯器將強(qiáng)制類(lèi)型轉(zhuǎn)換(沒(méi)有泛型的話,程序員會(huì)指定這些強(qiáng)制類(lèi)型轉(zhuǎn)換)插入生成的字節(jié)碼中。
命名類(lèi)型參數(shù)
推薦的命名約定是使用大寫(xiě)的單個(gè)字母名稱作為類(lèi)型參數(shù)。對(duì)于常見(jiàn)的泛型模式,推薦的名稱是:
- K:鍵,比如映射的鍵
- V:值,比如 List 和 Set 的內(nèi)容,或者 Map 中的值
- E:元素
- T:泛型
public class Generic<T> { //key的類(lèi)型為T(mén) private T key; public Generic(T key) { //泛型構(gòu)造方法形參key的類(lèi)型也為T(mén) this.key = key; } public T getKey() { //泛型方法getKey的返回值類(lèi)型為T(mén) return key; } }
如上定義了一個(gè)普通的泛型類(lèi),成員變量的類(lèi)型為 T,T的類(lèi)型由外部指定。泛型方法和泛型構(gòu)造函數(shù)同樣如此。
Generic<Integer> genericInteger = new Generic<Integer>(123456); //1 Generic<String> genericString = new Generic<String>("key_vlaue"); // 2 System.out.println("key is " + genericInteger.getKey()); System.out.println("key is " + genericString.getKey());
泛型的類(lèi)型參數(shù)只能是類(lèi)類(lèi)型(包括自定義類(lèi)),不能是簡(jiǎn)單類(lèi)型。傳入的實(shí)參類(lèi)型需與泛型的類(lèi)型參數(shù)類(lèi)型相同,即為Integer/String。
如上所述,定義的泛型類(lèi),就一定要傳入泛型類(lèi)型實(shí)參么?
并不是這樣,在使用泛型的時(shí)候如果傳入泛型實(shí)參,則會(huì)根據(jù)傳入的泛型實(shí)參做相應(yīng)的限制,此時(shí)泛型才會(huì)起到本應(yīng)起到的限制作用。如果不傳入泛型類(lèi)型實(shí)參的話,在泛型類(lèi)中使用泛型的方法或成員變量定義的類(lèi)型可以為任何的類(lèi)型。
Generic genericString = new Generic("111111"); Generic genericInteger = new Generic(4444); System.out.println("key is " + genericString.getKey()); System.out.println("key is " + genericInteger.getKey());
如上的代碼片段,將會(huì)輸出如下的結(jié)果:
key is 111111
key is 4444
在不傳入泛型類(lèi)型實(shí)參的情況下,泛型類(lèi)中使用的泛型防范或成員變量可以為 Integer 或 String 等等其他任意類(lèi)型。不過(guò)需要注意的是,泛型的類(lèi)型參數(shù)只能是類(lèi)類(lèi)型,不能是簡(jiǎn)單類(lèi)型。且不能對(duì)確切的泛型類(lèi)型使用 instanceof 操作。對(duì)于不同傳入的類(lèi)型實(shí)參,生成的相應(yīng)對(duì)象實(shí)例的類(lèi)型是不是一樣的呢?具體看如下的示例:
public class GenericTest { public static void main(String[] args) { Generic<Integer> name = new Box<String>("111111"); Generic<String> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); System.out.println("age class:" + age.getClass()); System.out.println(name.getClass() == age.getClass()); // true } }
由輸出結(jié)構(gòu)可知,在使用泛型類(lèi)時(shí),雖然傳入了不同的泛型實(shí)參,但并沒(méi)有真正意義上生成不同的類(lèi)型,傳入不同泛型實(shí)參的泛型類(lèi)在內(nèi)存上只有一個(gè),即還是原來(lái)的最基本的類(lèi)型(本例中為 Generic),當(dāng)然在邏輯上我們可以理解成多個(gè)不同的泛型類(lèi)型。
究其原因,在于 Java 中的泛型這一概念提出的目的,其只是作用于代碼編譯階段。在編譯過(guò)程中,對(duì)于正確檢驗(yàn)泛型結(jié)果后,會(huì)將泛型的相關(guān)信息擦除。也就是說(shuō),成功編譯過(guò)后的 class 文件中是不包含任何泛型信息的。泛型信息不會(huì)進(jìn)入到運(yùn)行時(shí)階段。
泛型類(lèi)型在邏輯上看以看成是多個(gè)不同的類(lèi)型,實(shí)際上都是相同的基本類(lèi)型。
通配符
Ingeter 是 Number 的一個(gè)子類(lèi),同時(shí) Generic<Ingeter> 與 Generic<Number> 實(shí)際上是相同的一種基本類(lèi)型。那么問(wèn)題來(lái)了,在使用 Generic<Number> 作為形參的方法中,能否使用Generic<Ingeter> 的實(shí)例傳入呢?在邏輯上類(lèi)似于 Generic<Number> 和 Generic<Ingeter> 是否可以看成具有父子關(guān)系的泛型類(lèi)型呢?下面我們通過(guò)定義一個(gè)方法來(lái)驗(yàn)證。
public void show(Generic<Number> obj) { System.out.println("key value is " + obj.getKey()); }
進(jìn)行如下的調(diào)用
Generic<Integer> genericInteger = new Generic<Integer>(123); show(genericInteger); //error Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
通過(guò)提示信息我們可以看到 Generic<Integer> 不能被看作為 Generic<Number> 的子類(lèi)。由此可以看出:同一種泛型可以對(duì)應(yīng)多個(gè)版本(因?yàn)閰?shù)類(lèi)型是不確定的),不同版本的泛型類(lèi)實(shí)例是不兼容的。
我們不能因此定義一個(gè) show(Generic<Integer> obj)來(lái)處理,因此我們需要一個(gè)在邏輯上可以表示同時(shí)是Generic和Generic父類(lèi)的引用類(lèi)型。由此類(lèi)型通配符應(yīng)運(yùn)而生。
T、K、V、E 等泛型字母為有類(lèi)型,類(lèi)型參數(shù)賦予具體的值。除了有類(lèi)型,還可以用通配符來(lái)表述類(lèi)型,? 未知類(lèi)型,類(lèi)型參數(shù)賦予不確定值,任意類(lèi)型只能用在聲明類(lèi)型、方法參數(shù)上,不能用在定義泛型類(lèi)上。將方法改寫(xiě)成如下:
public void show(Generic<?> obj) { System.out.println("key value is " + obj.getKey()); }
此處 ? 是類(lèi)型實(shí)參,而不是類(lèi)型形參。即和 Number、String、Integer 一樣都是實(shí)際的類(lèi)型,可以把 ? 看成所有類(lèi)型的父類(lèi),是一種真實(shí)的類(lèi)型??梢越鉀Q當(dāng)具體類(lèi)型不確定的時(shí)候,這個(gè)通配符就是 ?;當(dāng)操作類(lèi)型時(shí),不需要使用類(lèi)型的具體功能時(shí),只使用 Object 類(lèi)中的功能。那么可以用 ? 通配符來(lái)表未知類(lèi)型。
泛型上下邊界
在使用泛型的時(shí)候,我們還可以為傳入的泛型類(lèi)型實(shí)參進(jìn)行上下邊界的限制,如:類(lèi)型實(shí)參只準(zhǔn)傳入某種類(lèi)型的父類(lèi)或某種類(lèi)型的子類(lèi)。為泛型添加上邊界,即傳入的類(lèi)型實(shí)參必須是指定類(lèi)型的子類(lèi)型。
public void show(Generic<? extends Number> obj) { System.out.println("key value is " + obj.getKey()); }
我們?cè)诜盒头椒ǖ娜雲(yún)⑾薅▍?shù)類(lèi)型為 Number 的子類(lèi)。
Generic<String> genericString = new Generic<String>("11111"); Generic<Integer> genericInteger = new Generic<Integer>(2222); showKeyValue1(genericString); // error showKeyValue1(genericInteger);
當(dāng)我們的入?yún)?String 類(lèi)型時(shí),編譯報(bào)錯(cuò),因?yàn)?String 類(lèi)型并不是 Number 類(lèi)型的子類(lèi)。
類(lèi)型通配符上限通過(guò)形如 Generic<? extends Number> 形式定義;相對(duì)應(yīng)的,類(lèi)型通配符下限為Generic<? super Number>形式,其含義與類(lèi)型通配符上限正好相反,在此不作過(guò)多闡述。
泛型數(shù)組
在 java 中是不能創(chuàng)建一個(gè)確切的泛型類(lèi)型的數(shù)組的,即:
List<String>[] ls = new ArrayList<String>[10];
如上會(huì)編譯報(bào)錯(cuò),而使用通配符創(chuàng)建泛型數(shù)組是可以的:
List<?>[] ls = new ArrayList<?>[10]; //List<String>[] ls = new ArrayList[10];
JDK1.7 對(duì)泛型的簡(jiǎn)化,所以另一種聲明也是可以的。
由于JVM泛型的擦除機(jī)制,在運(yùn)行時(shí) JVM 是不知道泛型信息的。泛型數(shù)組實(shí)際的運(yùn)行時(shí)對(duì)象數(shù)組只能是原始類(lèi)型( T[]為Object[],Pair[]為Pair[] ),而實(shí)際的運(yùn)行時(shí)數(shù)組對(duì)象可能是T類(lèi)型( 雖然運(yùn)行時(shí)會(huì)擦除成原始類(lèi)型 )。成功創(chuàng)建泛型數(shù)組的唯一方式就是創(chuàng)建一個(gè)被擦出類(lèi)型的新數(shù)組,然后對(duì)其轉(zhuǎn)型。
public class GenericArray<T> { private Object[] array; //維護(hù)Object[]類(lèi)型數(shù)組 @SupperessWarning("unchecked") public GenericArray(int v) { array = new Object[v]; } public void put(int index, T item) { array[index] = item; } public T get(int index) { return (T)array[index]; } //數(shù)組對(duì)象出口強(qiáng)轉(zhuǎn) public T[] rep() { return (T[])array; } //運(yùn)行時(shí)無(wú)論怎樣都是Object[]類(lèi)型 public static void main (String[] args){ GenericArray<Integer> ga = new GenericArray<Integer>(10); // Integer[] ia = ga.rep(); //依舊ClassCastException Object[] oa = ga.rep(); //只能返回對(duì)象數(shù)組類(lèi)型為Object[] ga.put(0, 11); System.out.println(ga.get(0)); // 11 } }
在運(yùn)行時(shí),數(shù)組對(duì)象的出口做轉(zhuǎn)型輸出,入口方法在編譯期已實(shí)現(xiàn)類(lèi)型安全,所以出口方法可以放心強(qiáng)制類(lèi)型轉(zhuǎn)換,保證成功。
小結(jié)
本文主要講了 Java 泛型的相關(guān)概念和應(yīng)用。泛型使編譯器可以在編譯期間對(duì)類(lèi)型進(jìn)行檢查以提高類(lèi)型安全,減少運(yùn)行時(shí)由于對(duì)象類(lèi)型不匹配引發(fā)的異常。由泛型的誕生介紹相關(guān)的概念,在保證代碼質(zhì)量的情況下,如何使用泛型去簡(jiǎn)化開(kāi)發(fā)。
以上所述是小編給大家介紹的Java泛型及其應(yīng)用詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
解決Mybatis-plus和pagehelper依賴沖突的方法示例
這篇文章主要介紹了解決Mybatis-plus和pagehelper依賴沖突的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Struts2+Hibernate實(shí)現(xiàn)數(shù)據(jù)分頁(yè)的方法
這篇文章主要介紹了Struts2+Hibernate實(shí)現(xiàn)數(shù)據(jù)分頁(yè)的方法,結(jié)合實(shí)例形式分析了Struts2結(jié)合Hibernate實(shí)現(xiàn)數(shù)據(jù)分頁(yè)的原理,步驟與相關(guān)實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-03-03xxl-job對(duì)比ElasticJob使用示例詳解
這篇文章主要為大家介紹了xxl-job對(duì)比ElasticJob使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06springboot實(shí)現(xiàn)執(zhí)行sql語(yǔ)句打印到控制臺(tái)
這篇文章主要介紹了springboot實(shí)現(xiàn)執(zhí)行sql語(yǔ)句打印到控制臺(tái)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Spring?Boot開(kāi)發(fā)RESTful接口與http協(xié)議狀態(tài)表述
這篇文章主要為大家介紹了Spring?Boot開(kāi)發(fā)RESTful接口與http協(xié)議狀態(tài)表述,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03Spring實(shí)現(xiàn)Quartz自動(dòng)配置的方法詳解
這篇文章主要介紹了Spring實(shí)現(xiàn)Quartz自動(dòng)配置的方法詳解,如果想在應(yīng)用中使用Quartz任務(wù)調(diào)度功能,可以通過(guò)Spring Boot實(shí)現(xiàn)Quartz的自動(dòng)配置,以下介紹如何開(kāi)啟Quartz自動(dòng)配置,以及Quartz自動(dòng)配置的實(shí)現(xiàn)過(guò)程,需要的朋友可以參考下2023-11-11java中構(gòu)造器內(nèi)部調(diào)用構(gòu)造器實(shí)例詳解
在本篇文章里小編給大家分享的是關(guān)于java中構(gòu)造器內(nèi)部調(diào)用構(gòu)造器實(shí)例內(nèi)容,需要的朋友們可以學(xué)習(xí)下。2020-05-05