管中窺虎
在學習
java 1.5
的過程中,我使用了
sun
公布的
tutorial
,這份文檔寫的比較詳盡易明,但是對于想快速了解
tiger
而且具有較好
java
基礎(chǔ)的人來說,大篇幅的英文文檔是比較耗時間和非必需的,所以我將會歸納這份文檔的主要內(nèi)容,在保證理解的底線上,盡力減少閱讀者需要的時間。
在以下地址可以進入各新增語言特色介紹以及下載相關(guān)文檔(若有)。
http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html
這一篇是接著上文繼續(xù)的,在這里補充說明,雖然我希望以雙語寫作,但是把英文文檔翻譯過來后再翻譯回去,似乎是件好傻的事情。。。所以這些翻譯并精簡的文章算是個例外吧。
第一道虎紋:
generic
-泛型
/
類屬(二)
泛型方法
假設(shè)我們想把一個數(shù)組的元素都放到一個容器類里,下面是第一次嘗試:







?????????c.add(o);? // ?compile?time?error?




現(xiàn)在你應(yīng)該學會了不去犯初學者的錯誤,用
Collection < Object > 來作為參數(shù),你也許也發(fā)現(xiàn)了用 Collection < ? > 也不成,不知道就是不知道,不能放東西進去。
好了,主角登場:泛型方法







?????????c.add(o);? // ?correct?




方法的聲明加入了類型參數(shù),在上面這個方法里,如果
c 的元素類型是 a 的元素類型的父類,就能成功執(zhí)行方法。下面這些代碼可以幫助你了解一下:


































注意到我們并沒有真正的傳入一個類型實參,而是由編譯器以方法的實際參數(shù)對象來推斷,它推斷出使得這次方法調(diào)用成立的類型,并盡可能地特化這個類型。比如說如果
T 推斷為 Number 依然成立的時候,就不會推斷為 Object 。
現(xiàn)在看來,泛型方法和通配符有些共通的地方,使得類屬有一定的靈活性。那么什么時候用泛型方法,什么時候用通配符?看看下面的例子:









以及:











這兩種方式都達成了同樣的目的,使得方法有了多態(tài)性。然而注意到在每個方法的聲明中,
T 只出現(xiàn)了一次,這種情況下就應(yīng)該用通配符,通配符的主要目的就是提供彈性的泛化,而多態(tài)方法則用于表達兩個或多個參數(shù)間的依賴關(guān)系,你回過頭去看多態(tài)方法的第一個例子,是不是這個情況?如果不存在依賴關(guān)系需要表達,就不應(yīng)該用多態(tài)方法,因為從可讀性上來說,通配符更清晰。
而且有趣的是,它們并非水火不容,反而可以精妙配合,如下:










這個合作使得
dest 與 src 的依賴關(guān)系得以表達,同時讓 src 的接納范疇擴大了。假如我們只用泛型方法來實現(xiàn):










那么
S 的存在就顯得有些不必要,有些不優(yōu)雅。總的來說,通配符更簡潔清晰,只要情況允許就應(yīng)該首選。
下面是給幾何圖形家族添加了一個有記憶功能的繪圖方法,展示了泛型方法的使用,有興趣就看看,略過也不影響下一步的學習。
?




















?
這里又再談?wù)劽?guī)范的事情,用
T
來表示類型(
type
)就是個很好的選擇,假如已經(jīng)沒有更多的背景信息的話,而在泛型方法里就是這樣子,我們只是想表達一個類型。那么如果有多個參數(shù)出現(xiàn),那么用
T
的街坊鄰里就不錯,
S
啊,什么的。如果方法出現(xiàn)在一個泛型類里,就主要避免類的泛型變量和方法的泛型變量同名混淆,同樣的,泛型類的嵌套泛型類也應(yīng)該注意。
和遺老們打交道
很顯然的,這個星球上存在的
java
代碼里,沒有引入泛型的還是多數(shù),它們也不會一夜走進新社會,怎么把它們轉(zhuǎn)換為泛型的會在晚些再談及,現(xiàn)在我們談?wù)労退鼈兇蚪坏赖氖虑椤_@包括兩方面:在引入泛型的代碼里使用老代碼,在老代碼上使用引入了泛型的代碼。
首先看看前者,看例子:





































































上面的代碼有沒有問題?如果前面的內(nèi)容里你沒打瞌睡,你應(yīng)該發(fā)現(xiàn)注解處的那個語句很有問題,類型不安全問題。我們稱Collection這種不帶類型參數(shù)的使用叫做原始類型,編譯器無法保證這樣子的容器類放了什么東西,但是編譯器會以不那么嚴格的標準去要求這個舊社會的人,否則,老代碼將完全不能在1.5里使用,編譯器會發(fā)出一個unchecked warning,怎么處理由你來決定。這樣的設(shè)計是符合實際的,否則就是和已有代碼徹底決裂。
雖然這樣的調(diào)用會有錯誤的風險,但總比你完全不用泛型機制好,因為至少你保證了在你的這一端的類型安全,而且總有一天,英特那雄耐爾一定會實現(xiàn)。。。。
J
嚴肅地回到我們的話題,既然有風險,那么當你得到了這樣的警告時,小心檢查則是目前最好的對策。
但是,如果你不理會一個這樣的警告,而且事實上你真的犯了一個類型安全的錯誤,會發(fā)生什么呢?
消除與翻譯











這個警告所在的地方確實是有問題的,一個
Integer 被放入了一個 List 中,這個 List 只是原始類型,所以編譯器只能給個警告,但事實上它又是指向了 ys 指向的對象,一個只放 String 的 List ,在最后一句里,會發(fā)生什么事情?實際運行起來,這段代碼就等同于:













它們會得到同樣的錯誤:一個
ClassCastException 。
為什么呢?因為泛型在
java
編譯器里是以一種稱為“消除”的前端轉(zhuǎn)換實現(xiàn)的,你幾乎,我說幾乎,可以認為是一種代碼到代碼的翻譯,象上面這樣,把帶泛型的版本翻譯成不帶泛型的版本,接下來,當代碼的執(zhí)行交到
JVM
的手里時,它可不管你是哪朝代的人,有錯就是有錯,類型安全的基本政策不動搖,即使你手里拽著
unchecked warnings
的證明。
基本上,消除機制就是把類型信息都扔掉了,
List<String>
變成了
List
,上限通常都變成了
Object
,而且,當轉(zhuǎn)換后的代碼不符合泛型里的類型限制時,就添加一個類型轉(zhuǎn)換,就是上例中那個
String
的轉(zhuǎn)換會出現(xiàn)的原因。
消除機制的細節(jié)不是這里討論的內(nèi)容,這里簡單的讓你了解一些需要了解的情況而已。
假如你的代碼更新為:













而調(diào)用這個代碼的老客戶代碼是:






























?
也就是在老代碼里調(diào)用泛型化了的代碼。
如注解
1
所示,會有警告出現(xiàn),原因你也已經(jīng)了解了。你可以在編譯時選擇用
1.4
的環(huán)境,那么就不會有警告出現(xiàn),但同樣你也失去了
1.5
帶來的各種新特色了。
這一篇到此為止,下一篇將看看一些還需要補充說明的地方,但并不意味著內(nèi)容會很簡單。
?