14 泛型

14.1 泛型與集合

? ? ? ? JDK1.5增加了泛型支持。增加泛型后的集合,可以讓代碼更加簡潔,程序更加健壯。Java泛型可以保證,如果程序在編譯時沒有發(fā)出警告,運(yùn)行時就不會產(chǎn)生ClassCastException異常。

? ? ? ? 泛型,即參數(shù)化類型,允許程序在創(chuàng)建集合時指定集合元素的類型。Java5改寫了集合框架中的全部接口和類,為這些接口和類增加了泛型支持。創(chuàng)建這種特殊集合的方法是:在集合接口或集合類后增加尖括號,尖括號里放一個數(shù)據(jù)類型,表明這個集合接口或集合類只能保存特定類型的對象。

? ? ? ? 集合自動記住所有集合元素的數(shù)據(jù)類型,無須對集合元素進(jìn)行強(qiáng)制類型轉(zhuǎn)換。

? ? ? ? Java7開始,Java允許在構(gòu)造器后不需要完整的泛型信息,只要給出一對尖括號即可,Java可以推斷尖括號里應(yīng)該是什么泛型信息。此方式稱為菱形語法。

14.2 聲明泛型

? ? ? ? 泛型允許在定義類、接口、方法時聲明類型形參,這個類型形參的將在聲明變量、創(chuàng)建對象、調(diào)用方法時動態(tài)的指定。類型形參可當(dāng)成類型使用,幾乎所有可使用普通類型的地方都可以使用類型形參。

? ? ? ? 包含泛型聲明的類型可以在定義變量、創(chuàng)建對象時傳入一個類型實參,從而可以動態(tài)地生成多個邏輯上的子類,但這種子類在物理上并不存在

? ? ? ? 當(dāng)創(chuàng)建帶泛型聲明的自定義類時,定義構(gòu)造器時,構(gòu)造器不要添加泛型聲明

14.3 泛型類派生子類

? ? ? ? 使用泛型接口或泛型類派生子類時,泛型接口和泛型父類不能再包含類型形參。要么不為類型形參傳入實際的類型參數(shù),此時系統(tǒng)會把類里的T形參當(dāng)成Object類型處理;要么傳入一個類型,則所有的T都被當(dāng)成傳入的類型處理。

? ? ? ? 不存在泛型類:不管為泛型的類型形參傳入什么類型實參,它依然被當(dāng)成同一個類處理,在內(nèi)存中只占用同一塊內(nèi)存空間。所以靜態(tài)方法、靜態(tài)初始化塊和靜態(tài)變量的聲明和初始化中不允許使用類型形參,如static T info;或static T func();都是錯誤的。

? ? ? ? 由于系統(tǒng)不生成泛型類,所以instanceof運(yùn)算符后不能使用泛型類。

14.4 類型通配符

? ? ? ? 如果B繼承了A類,Collection<B>不是Collection<A>類的子類。為了表示各種泛型集合的父類,可以使用類型通配符(?),寫作Collection<?>、List<?>、Set<?>。

? ? ? ? 注意:如果一個方法由一個List<?>參數(shù),則此方法傳入任何類型的List都可以,方法里L(fēng)ist的參數(shù)的類型是未知的,但可以認(rèn)為是Object類型。因為程序無法確定此List集合中元素的類型,所以不能向其中添加元素。因為add方法默認(rèn)接受一個泛型類型的參數(shù)E,這個類型形參將會在定義List時推斷出,但是List的類型沒有被指定,而是?,所以add不能準(zhǔn)確知道E時何種類型。但是使用get方法可以獲取某個值,但它的類型依然未知,可以當(dāng)做Object使用。

????????設(shè)定類型通配符的上限:當(dāng)程序中不希望List<?>表示所有的List集合的父類,而是只表示一類泛型List類型的父類時,可以使用菱形通配符上限。如List<? extends Number>表示此List是所有數(shù)值型List集合的父類。這個Number被稱為類型通配符的上限。

? ? ? ? 設(shè)定類型形參的上限:使用類型形參上限,表示傳給該類型形參的實際類型要么是該上限類型,要么是上限類型的子類。類型形參最多只能有一個父類上限,可以有多個接口上限,此時類型形參必須是繼承父類并且實現(xiàn)了所有接口的類,如class Wang<T extends Number & Comparable>。與定義類規(guī)則相同,所有的接口必須位于父類之后。

? ? ? ? 設(shè)定類型通配符下限:如TreeSet的構(gòu)造方法TreeSet(Comparator < ??super E> c),設(shè)定通配符的上限。假設(shè)需要創(chuàng)建一個TreeSet,那么Comparator的泛型類型就可以為String或者Object類型,使用方式更加靈活。一個方法可以通過通配符上限和下限兩種方式進(jìn)行重載,但程序調(diào)用時會引發(fā)混亂。

14.5 泛型方法

? ? ? ? 定義接口和類時沒有使用類型形參,但定義方法時可以自己定義類型形參。泛型方法,即聲明方法時定義了一個或多個類型形參的方法,Java5開始提供。泛型方法簽名比普通方法簽名多了類型形參聲明,尖括號包裹類型形參,多個類型形參以逗號分隔,類型形參聲明在方法修飾符和返回值類型之間(修飾符 <T, S> 返回值類型 方法名(形參列表) { 方法體 })。

? ? ? ? 方法聲明中定義的形參只能在該方法里使用,而接口和類中聲明的類型形參可以在這個接口或類中使用。與類和接口不同的是,方法中的泛型參數(shù)無須顯式傳入實際類型參數(shù),編譯器根據(jù)實參的類型可以自動推斷出最直接的類型。

14.6 泛型方法和類型通配符的區(qū)別

? ? ? ? 泛型方法作用:泛型方法允許類型形參被用來表示方法的一個或多個參數(shù)之間的類型依賴關(guān)系,或者方法返回值與參數(shù)之間的類型以來關(guān)系。

? ? ? ? 類型通配符方法的作用:可以在不同的調(diào)用點傳入不同的實際類型,用來支持靈活的子類化。

? ? ? ? 綜上所述,如果某個方法中一個形參a的類型或返回值的類型依賴于另一個形參b的類型,則形參b的類型聲明不可以使用通配符,因為如果形參b的類型無法確定,程序就無法定義形參a的類型。此時應(yīng)該使用泛型方法。但是,如果形參a,b都是集合,而又無需修改形參a,則可以用通配符來定義形參a的類型。因為,如果為形參a單獨指定一個類型,即使形參a的類型依賴形參b的類型,但因為形參a的類型只出現(xiàn)一次,所以用通配符更簡潔。

? ? ? ? 類型通配符可以在方法簽名中定義形參的類型,也可以定義引用變量的類型;泛型方法中的類型形參必須在對應(yīng)方法中顯式聲明。

14.7 泛型構(gòu)造器

? ? ? ? 在構(gòu)造器的簽名中聲明類型形參,即為泛型構(gòu)造器。在調(diào)用構(gòu)造器創(chuàng)建對象時,可以指定類型形參的實際類型,也可以使用菱形語法讓編譯器自己推斷。

? ? ? ? 但是,如果程序顯示指定了泛型構(gòu)造器中聲明的類型形參的實際類型,則不可以使用菱形語法,即需要為構(gòu)造器后的菱形括號傳入實際的類型。

? ? ? ? 如new Me<>();或new <A> Me<B>();

14.8 Java8改進(jìn)的類型推斷

? ? 包含如下兩個方面:

? ? ? ? - 可以通過調(diào)用方法的上下文來推斷類型的目標(biāo)類型。

? ? ? ? - 可以在方法調(diào)用鏈中,將推斷得到的類型參數(shù)傳遞到最后一個方法。

14.9 擦除和轉(zhuǎn)換

????????如果沒有為這個泛型指定實際的類型參數(shù),則該類型參數(shù)被稱作原始類型(raw type),默認(rèn)是生命該類型參數(shù)時指定的第一個上限類型。? ? ? ??

? ? ? ? 當(dāng)把一個具有泛型信息的對象賦給另一個沒有泛型信息的變量時,所有在尖括號之間的類型信息都將被擦除。當(dāng)把一個沒有類型信息的對象賦值給一個帶有類型信息的變量時,編譯可以通過,但是提示“未經(jīng)檢查的轉(zhuǎn)換”警告,在強(qiáng)制類型轉(zhuǎn)換時,可能引發(fā)異常。

14.10 總結(jié)

? ? ? ? - 泛型類型不能被實例化,因為創(chuàng)建對象在運(yùn)行時進(jìn)行,而類型在編譯時就被擦除了,所以編譯器不知道該使用何種類型。如下方式均不可以:T t = new T(); T[] array = new T[5];

? ? ? ? - 不能創(chuàng)建類型特定的泛型引用變量的數(shù)組,如A<String>[] a = new A<String>[10];是不允許的。但是可以聲明特定的泛型引用變量的數(shù)組變量,如A<String>[] a = new A[10];是可以的,編譯時會出現(xiàn)“未經(jīng)檢查的轉(zhuǎn)換”警告,運(yùn)行時可能出現(xiàn)ClassCastException??梢远x帶無上限類型通配符的數(shù)組,如A<?> a = new A<?>[10];是可以的,但是需要自己使用instanceof控制強(qiáng)制類型轉(zhuǎn)換,避免程序運(yùn)行時出現(xiàn)錯誤。

? ? ? ? - 不能將基本類型賦給類型形參作為實際的類型,只能使用引用類型。

? ? ? ? - 不能拋出(throw)也不能捕獲(catch)泛型類的異常對象,如throw new T(); catch(A<T> e) {};是錯誤的;也不能讓泛型類型繼承Throwable(AExcep<T> extends Throwable);不能catch(T e);但可以在方法簽名中聲明拋出泛型異常(throws T)。

? ? ? ? - 靜態(tài)變量和靜態(tài)方法不能用類型參數(shù),但可以聲明靜態(tài)泛型方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容