你可以通過(guò)指定extends 后接類型名稱來(lái)提供通配符的上界。同樣的,你可以通過(guò)指定super 后接類型名稱來(lái)提供通配符的下界。這些限定限制了可以作為實(shí)際類型參數(shù)傳入的類型。
在例子中,你可以把? extends String 理解為任何String或其子類的實(shí)際類型參數(shù)。同樣的,你可以把? super String 理解為任何String或其父類的實(shí)際類型參數(shù)。因?yàn)镾tring是final的,這意味著它不能被繼承,只有源列表為String對(duì)象,目標(biāo)列表為String或Object對(duì)象能夠傳入作為參數(shù),這樣用處不大。
你可以使用泛型方法來(lái)完全解決這個(gè)問(wèn)題,它是一個(gè)有類型實(shí)現(xiàn)參數(shù)的類或接口方法。泛型方法支持下面的語(yǔ)法:
1 2 | <formalTypeParameterList> returnType
identifier(parameterList)
|
泛型方法的形參列表在它的返回類型之前。它包含類型參數(shù)和可選的上界。類型參數(shù)可以作為返回類型使用,并且可以出現(xiàn)在參數(shù)列表中。
清單9展示了怎么定義和調(diào)用泛型copy() 方法
Listing 9. GenDemo.java (version 5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import java.util.ArrayList;
import java.util.List;
public class GenDemo
{
public static void main(String[] args)
{
List<Integer> grades = new ArrayList<Integer>();
Integer[] gradeValues =
{
new Integer( 96 ),
new Integer( 95 ),
new Integer( 27 ),
new Integer( 100 ),
new Integer( 43 ),
new Integer( 68 )
};
for ( int i = 0 ; i < gradeValues.length; i++)
grades.add(gradeValues[i]);
List<Integer> failedGrades = new ArrayList<Integer>();
copy(grades, failedGrades, new Filter<Integer>()
{
public boolean accept(Integer grade)
{
return grade.intValue() <= 50 ;
}
});
for ( int i = 0 ; i < failedGrades.size(); i++)
System.out.println(failedGrades.get(i));
}
static <T> void copy(List<T> src, List<T> dest,
Filter<T> filter)
{
for ( int i = 0 ; i < src.size(); i++)
if (filter.accept(src.get(i)))
dest.add(src.get(i));
}
}
interface Filter<T>
{
boolean accept(T o);
}
|
清單9中我定義了一個(gè)<T> void copy(List<T> src, List<T> dest, Filter<T> filter) 泛型方法。編譯器注意到src ,dest 和filter 參數(shù)的類型都包含類型參數(shù)T。這意味著在方法調(diào)用中必須傳入同樣的實(shí)際類型參數(shù),而編譯器會(huì)在調(diào)用中獲取參數(shù)。
如果你編譯清單9(javac GenDemo.java )并運(yùn)行程序(java GenDemo ),你應(yīng)該可以看到下面的輸出:
Java語(yǔ)言中關(guān)于泛型最有爭(zhēng)議的是什么?
雖然泛型本身并不具爭(zhēng)議,但它在Java語(yǔ)言中的特殊實(shí)現(xiàn)卻是。泛型是作為消除轉(zhuǎn)換的語(yǔ)法糖的編譯時(shí)特性來(lái)實(shí)現(xiàn)的。編譯器會(huì)在編譯源碼后丟棄泛型類型或泛型的形參類型列表。這個(gè)“丟棄”行為稱為擦除(erasure)。其他在泛型中關(guān)于擦除的例子包含:在代碼類型不正確時(shí),插入時(shí)可以自動(dòng)轉(zhuǎn)換為合適的類型;通過(guò)上界(例如Object )來(lái)替換類型參數(shù)。
更多關(guān)于泛型的討論
泛型不只因?yàn)椴脸鴤涫軤?zhēng)議??匆幌耂tackOverflow.com的“為什么我們抱怨Java關(guān)于泛型的實(shí)現(xiàn)很糟糕”主題的討論,包含了通配符很難理解和事實(shí)上泛型并不直接值類型(例如,你不能指定List<int> )。
使用擦除會(huì)有下面的幾個(gè)限制:
-
instanceof 并不能用于參數(shù)化類型,只有一種情況是例外的。這個(gè)例外就是無(wú)界的通配符。例如,你不能指定Set<Shape> shapes = null; if (shapes instanceof ArrayList<Shape>){} 。相反,你需要把對(duì)instanceof 表達(dá)式修改為shapes instanceof ArrayList<?> ,這種就是無(wú)界的通配符?;蛘撸憧梢灾付?code>shapes instanceof ArrayList,這使用的是原生類型(通常也是推薦使用的做法)。
-
編譯器把泛型代碼轉(zhuǎn)換為非泛型代碼,并保存在class文件中。一些開(kāi)發(fā)人員指出擦除會(huì)使得你不能通過(guò)反射取得泛型信息,因?yàn)樗鼈儾⒉槐4嬖赾lass文件中。開(kāi)發(fā)人員Jakob Jenkov在“Java 反射:泛型”中指出一些泛型信息會(huì)被保存在class文件中的情況,并且這些信息可以通過(guò)反射來(lái)訪問(wèn)。
-
你不能在創(chuàng)建數(shù)組的表達(dá)式中使用類型參數(shù);例如,elements = new E[size]; 。如果你這樣做,編譯器會(huì)報(bào)告泛型數(shù)組創(chuàng)建錯(cuò)誤信息。
鑒于擦除的限制,你會(huì)奇怪為什么泛型要通過(guò)擦除來(lái)實(shí)現(xiàn)。原因很簡(jiǎn)單:Java編譯器被重構(gòu)來(lái)使用擦除,因此泛型代碼可以跟那些非泛型的遺留代碼進(jìn)行交互。沒(méi)有這個(gè)向后兼容性,遺留代碼在支持泛型的Java編譯器上編譯時(shí)將會(huì)報(bào)錯(cuò)。
第一部分總結(jié)
Java語(yǔ)言已經(jīng)添加了許多新特性。在這篇文章中,我展示了怎么使用斷言來(lái)增強(qiáng)你在代碼正確性上的信心,和如何使用泛型來(lái)消除ClassCastException 。通過(guò)使用斷言和泛型,你可以編寫(xiě)更可靠的代碼,并且使你的代碼在運(yùn)行時(shí)的錯(cuò)誤降到最低,當(dāng)然,也減少面對(duì)生氣的客戶時(shí)的頭痛了。
Java 5 是Java平臺(tái)歷史上的一個(gè)重大發(fā)布,雖然泛型比其他特性都更具爭(zhēng)議,但它卻比其他都更加重要。我的下篇文章將會(huì)介紹另外7個(gè)在Java5時(shí)加入的必要的特性:類型安全的枚舉,注解,自動(dòng)裝箱和拆箱,加強(qiáng)的循環(huán),靜態(tài)引入,可變參數(shù),協(xié)變返回類型。在那之前,下載這篇文章的源代碼,它包含了更多的關(guān)于斷言和泛型的提示和例子。
Jeff Friesen是一個(gè)自由職業(yè)導(dǎo)師和側(cè)重Java和Android的軟件開(kāi)發(fā)人員。除了為Apress寫(xiě)Java和Android書(shū)籍,Jeff為JavaWorld,informIT,Java.net,DevSource和SitePoint寫(xiě)了大量的關(guān)于Java和其他技術(shù)的文章。你可以通過(guò)他在TutorTutor.ca的網(wǎng)站聯(lián)系到他
了解更多主題相關(guān)
下載文章的源代碼
閱讀Angelika Langer的Java Generics FAQs,那里有著大量的關(guān)于Java語(yǔ)言泛型的信息和觀點(diǎn)。
對(duì)于想學(xué)習(xí)Java語(yǔ)言和它的備受爭(zhēng)議的特性的,Langer的文章理解閉包的爭(zhēng)論(2008.6 JavaWorld)對(duì)比了Java 7語(yǔ)言中的三個(gè)關(guān)于添加閉包和lambda表達(dá)式的初始提議。
可以查看“Java反射:泛型”(Jakob Jenkov, Jenkov.com)關(guān)于泛型反射和某些情況下可以在運(yùn)行時(shí)獲取泛型信息的討論。
Java無(wú)痛并發(fā)編程,第一部(2013.6):介紹了Executor框架,同步類型和Java并發(fā)集合包。
Java無(wú)痛并發(fā)編程,第二部(2013.8):介紹了鎖,原子變量和fork/join操作,還附加了Java8中關(guān)于java.util.concurrent 的修改概述。
跟上Java Date和Time API(2013.4):介紹了Java8的JSR310:Date 和Time API,并且展示了你最有可能使用的java.time 系列類的使用。
JavaWorld中更多關(guān)于Java集合框架的文章:
Java集合框架從零開(kāi)始(1998.11 Dan Becker):這篇文章介紹了集合剛引入java時(shí)的歷史。
Java集合中的省時(shí)習(xí)慣(2013.9 Java Q&A blog):展望未來(lái),Jeff Friesen回答一些當(dāng)前使用Java集合的常見(jiàn)問(wèn)題。
|