寫(xiě)的很好,保存一下.
從jdk1.5開(kāi)始,Java中開(kāi)始支持范型了。范型是一個(gè)很有用的編程工具,給我們帶來(lái)了極大的靈活性。在看了《java核心編程》之后,我小有收獲,寫(xiě)出來(lái)與大家分享。
所謂范型,我的感覺(jué)就是,不用考慮對(duì)象的具體類型,就可以對(duì)對(duì)象進(jìn)行一定的操作,對(duì)任何對(duì)象都能進(jìn)行同樣的操作。這就是靈活性之所在。但是,正是因?yàn)闆](méi)有 考慮對(duì)象的具體類型,因此一般情況下不可以使用對(duì)象自帶的接口函數(shù),因?yàn)椴煌膶?duì)象所攜帶的接口函數(shù)不一樣,你使用了對(duì)象A的接口函數(shù),萬(wàn)一別人將一個(gè)對(duì) 象B傳給范型,那么程序就會(huì)出現(xiàn)錯(cuò)誤,這就是范型的局限性。所以說(shuō),范型的最佳用途,就是用于實(shí)現(xiàn)容器類,實(shí)現(xiàn)一個(gè)通用的容器。該容器可以存儲(chǔ)對(duì)象,也可 以取出對(duì)象,而不用考慮對(duì)象的具體類型。因此,在學(xué)習(xí)范型的時(shí)候,一定要了解這一點(diǎn),你不能指望范型是萬(wàn)能的,要充分考慮到范型的局限性。下面我們來(lái)探討 一下范型的原理以及高級(jí)應(yīng)用。首先給出一個(gè)范型類:














我們看到,上述Pair類是一個(gè)容器類(我會(huì)多次強(qiáng)調(diào),范型天生就是為了容器類的方便實(shí)現(xiàn)),容納了2個(gè)數(shù)據(jù),但這2個(gè)數(shù)據(jù)類型是不確定的,用范型T來(lái)表示。關(guān)于范型類如何使用,那是最基本的內(nèi)容,在此就不討論了。
下面我們來(lái)討論一下Java中范型類的實(shí)現(xiàn)原理。在java中,范型是在編譯器中實(shí)現(xiàn)的,而不是在虛擬機(jī)中實(shí)現(xiàn)的,虛擬機(jī)對(duì)范型一無(wú)所知。因此,編譯器一 定要把范型類修改為普通類,才能夠在虛擬機(jī)中執(zhí)行。在java中,這種技術(shù)稱之為“擦除”,也就是用Object類型替換范型。上述代碼經(jīng)過(guò)擦除后就變成 如下形式:

















大家可以看到,這是一個(gè)普通類,所有的范型都被替換為Object類型,他被稱之為原生類。每當(dāng)你用一個(gè)具體類去實(shí)例化該范型時(shí),編譯器都會(huì)在原生類的基礎(chǔ)上,通過(guò)強(qiáng)制約束和在需要的地方添加強(qiáng)制轉(zhuǎn)換代碼來(lái)滿足需求,但是不會(huì)生成更多的具體的類(這一點(diǎn)和c++完全不同)。我們來(lái)舉例說(shuō)明這一點(diǎn):










下面我們?cè)賮?lái)考察一個(gè)更復(fù)雜的情況,如果我們的Pair類要保證第二個(gè)屬性一定要大于第一個(gè)屬性,該如何做?這就涉及到兩個(gè)屬性的比較,但是這2個(gè)屬性類 型未知,可以比較嗎?我們前面也講過(guò),一般情況下不要涉及類型的具體信息。但是現(xiàn)在要比較2個(gè)屬性,不得不涉及類型的具體信息了。Java還是考慮到了這 一點(diǎn),那就是,范型類可以繼承自某一個(gè)父類,或者實(shí)現(xiàn)某個(gè)接口,或者同時(shí)繼承父類并且實(shí)現(xiàn)接口。這樣的話,就可以對(duì)類型調(diào)用父類或接口中定義的方法了。代 碼如下:














我們看到,上面的范型T被我們添加了一個(gè)約束條件,那就是他必須實(shí)現(xiàn)Comparable接口,這樣的話,我們就可以對(duì)范型T使用接口中定義的方法了,也 就可以實(shí)現(xiàn)2個(gè)元素大小的比較。有人可能要問(wèn)了,實(shí)現(xiàn)一個(gè)接口不是用implements嗎?上面怎么用extends呢??為了簡(jiǎn)化范型的設(shè)計(jì),無(wú)論是 繼承類還是實(shí)現(xiàn)接口,一律使用extends關(guān)鍵字。這是規(guī)定,沒(méi)辦法,記住就行了。若同時(shí)添加多個(gè)約束,各個(gè)約束之間用“&”分隔,比 如:public class Pair<T extends Comparable & Serializable>。那么編譯器是如何處理這種情況呢?前面講過(guò),范型類最終都會(huì)被轉(zhuǎn)化為原生類。在前面沒(méi)有添加約束的時(shí)候,編譯器將范型 通通替換為Object;而增加了約束之后,通通用第一個(gè)約束來(lái)替換范型(上面的代碼就會(huì)用Comparable來(lái)替換所有范型),當(dāng)需要用到其他約束中定義的方法的時(shí)候,通過(guò)插入強(qiáng)制轉(zhuǎn)化代碼來(lái)實(shí)現(xiàn)。在此就不給出具體的例子了。
下面我們來(lái)看看最后一個(gè)知識(shí)點(diǎn),定義一個(gè)函數(shù),該函數(shù)接受一個(gè)范型類作為參數(shù)。首先讓我們來(lái)看一個(gè)最簡(jiǎn)單的情況,參數(shù)是一個(gè)實(shí)例化的范型類:



上述代碼中,形參list的元素被實(shí)例化為Number類型。在使用該函數(shù)的時(shí)候我們能不能傳入一個(gè)元素為Integer的list呢?看看下面代碼合法嗎?


答案上面已經(jīng)給出了:不行!對(duì)于這種形參,實(shí)參的類型必須和他完全一致,即也應(yīng)該是一個(gè)元素為Number的list才可以,其他的實(shí)參一律不行。這是為 什么呢?Integer不是Number的子類嗎?子類的對(duì)象傳遞給父類的引用,不可以嗎?這里我們就要注意了,Integer確實(shí)是Number的子類,但是,ArrayList<Integer>并不是ArrayList<Number>的子類,二者之間沒(méi)有任何的繼承關(guān)系!!因此這樣傳遞參數(shù)是不允許的。如果允許的話,會(huì)出現(xiàn)什么問(wèn)題嗎?當(dāng)然會(huì),我們對(duì)test函數(shù)重新定義一下:



大家可以看到,在函數(shù)內(nèi)部,我們把Float類型的元素插入到鏈表中。因?yàn)殒湵硎荖umber類型,這條語(yǔ)句沒(méi)問(wèn)題。但是,如果實(shí)參是一個(gè)Integer 類型的鏈表,他能存儲(chǔ)Float類型的數(shù)據(jù)嗎??顯然不能,這樣就會(huì)造成運(yùn)行時(shí)錯(cuò)誤。于是,編譯器干脆就不允許進(jìn)行這樣的傳遞。
通過(guò)分析我們看到,出錯(cuò)的可能性只有一個(gè):在向容器類添加內(nèi) 容的時(shí)候可能造成類型不匹配。那么有些人可能會(huì)有這種要求:“我保證一定不對(duì)容器添加內(nèi)容,我非常希望能夠?qū)⒁粋€(gè)Integer類(Number類的子 類)組成的鏈表傳遞進(jìn)來(lái)”。Sun的那幫大牛們當(dāng)然會(huì)考慮到這種訴求,這樣的功能是可以實(shí)現(xiàn)的,并且還有兩種方式呢,看下面代碼:














按照上述代碼的寫(xiě)法,只要我們對(duì)形參添加了一定的約束條件,那么我們?cè)趥鬟f實(shí)參的時(shí)候,對(duì)實(shí)參的嚴(yán)格約束就會(huì)降低一些。上述代碼都指定了一個(gè)類 Number,并用了extends關(guān)鍵字,因此,在傳遞實(shí)參的時(shí)候,凡是從Number繼承的類組成的鏈表,均可以傳遞進(jìn)去。但上面代碼的注釋中也說(shuō)的 很清楚,為了不出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,編譯器會(huì)對(duì)你調(diào)用的方法做嚴(yán)格的限制:凡是參數(shù)為范型的方法,一律不需調(diào)用!! l.get(0)是合法的,因?yàn)閰?shù)是整型而不是范型;l.add(x)就不合法,因?yàn)閍dd函數(shù)的參數(shù)是范型。但是定義一個(gè)范型方法還是有一定靈活性的,如果傳入的數(shù)據(jù)也是范型,編譯器還是認(rèn)可的,因?yàn)榉缎蛯?duì)范型,類型安全是可以保證的。
從上述代碼可以看出,定義一個(gè)范型方法要比Wildcard稍微靈活一些,可以往鏈表中添加T類型的對(duì)象,而Wildcard中是不允許往鏈表中添加任何類型的對(duì)象的。那么我們還要Wildcard干什么呢?Wildcard還是有他存在的意義的,那就是,Wildcard支持另外一個(gè)關(guān)鍵字super,而范型方法不支持super關(guān)鍵字。換句話說(shuō),如果你要實(shí)現(xiàn)這樣的功能:“傳入的參數(shù)應(yīng)該是指定類的父類”,范型方法就無(wú)能為力了,只能依靠Wildcard來(lái)實(shí)現(xiàn)。代碼如下:






難道“把取出的元素再插入到鏈表中”這樣一個(gè)功能就實(shí)現(xiàn)不了嗎?當(dāng)然可以,不過(guò)不能直接實(shí)現(xiàn),要借助范型函數(shù)的幫忙,因?yàn)樵诜缎秃瘮?shù)中,剛剛?cè)〕龅脑卦俅婊厝ナ遣怀蓡?wèn)題的。定義這樣一個(gè)范型函數(shù),我們稱之為幫助函數(shù)。代碼如下:












上述兩個(gè)函數(shù)結(jié)合的原理就是:利用Wildcard的super關(guān)鍵字來(lái)限制參數(shù)的類型(范型函數(shù)不支持super,要是支持的話就不用這么麻煩了),然后通過(guò)范型函數(shù)來(lái)完成取出數(shù)據(jù)的再存儲(chǔ)。
以上就是我學(xué)習(xí)范型的所有心得。下面再把《Java核心編程》中列出的使用范型時(shí)的注意事項(xiàng)列出來(lái)(各種操作被禁止的原因就不具體說(shuō)明了),供大家參考:













文章來(lái)源:http://wintys.blog.51cto.com/425414/89225