簡介
泛型其實(shí)并不是一種新的語言元素,C++中早就就有,但是在C++之后的java卻沒有吸收這個(gè)特性,現(xiàn)在Java也有了泛型的特性,大概也和.Net的競爭有關(guān)系吧。
首先看泛型的一個(gè)應(yīng)用。
在過去,我們可能經(jīng)常要寫一些類似這樣的代碼:
List stringList=new LinkedList();
stringList.add("firstString");
stringList.add("secondString");
String str=(String)stringList.iterator().next(); |
實(shí)際上第三行對(duì)String的類型轉(zhuǎn)換意義并不大,因?yàn)橥ǔN覀內(nèi)绻诓僮饕粋€(gè)List,都是知道這個(gè)List里面放的是什么類型對(duì)象的,但是我們?nèi)绻贿@樣寫又通不過語法檢查。
利用java的泛型機(jī)制,我們可以這么寫:
List<String> stringList=new LinkedList<String>();
stringList.add("firstString");
stringList.add("secondString");
String str=stringList.iterator().next(); |
這樣做的好處是在定義容器的時(shí)候就指明了容器中的類型,一方面我們不再需要取一個(gè)元素時(shí)候做強(qiáng)制類型轉(zhuǎn)換,另外一方面如果在這個(gè)容器中放入的對(duì)象類型不符合要求,那么會(huì)在編譯時(shí)候產(chǎn)生一個(gè)錯(cuò)誤,而不是在運(yùn)行時(shí)候才拋出一個(gè)異常。
另外這樣也提高了程序的可讀性。
泛型類型的定義
下面是一個(gè)簡單的使用泛型類的定義:
public class MyGenericClass<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
} |
值得注意的一點(diǎn)是,靜態(tài)變量不能夠使用泛型定義,也就是說類似下面的語句是非法的:
public class MyGenericClass<T> {
public static T value;//錯(cuò)誤的定義
}
|
此外,泛型的定義不會(huì)被繼承,舉個(gè)例子來說,如果A是B的子類,而C是一個(gè)聲明了泛型定義的類型的話,C<A>不是C<B>的子類。為了更好的說明,可以看下面的代碼,這段代碼是錯(cuò)誤的。
List<String> strList =new ArrayList<String>();
List<Object> objList=strList; //錯(cuò)誤的賦值 |
不過這樣一段代碼是正確的:
List<Object> strList =new ArrayList<Object>();
strList.add("a string"); |
統(tǒng)配類型
假設(shè)我們需要這樣一個(gè)函數(shù),使用它可以把一個(gè)集合中所有的元素打印出來,在以前我們可能這樣定義:
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++)
{
System.out.println(i.next());
}
} |
使用新的泛型特性我們可以這樣寫:
void printCollection(Collection<Object> c)
{
for (Object e : c)
{
System.out.println(e);
}
} |
但是這樣有一個(gè)問題,假如我們現(xiàn)在有個(gè)對(duì)象類型是Collection<String>,那么我們不能夠?qū)⑺鳛閰?shù)傳給printCollection,因?yàn)?/SPAN>Collection<String>并不是Collection<Object>的子類。
為了解決這個(gè)問題,我們可以使用統(tǒng)配類型?,也就是定義成下面這個(gè)樣子:
void printCollection(Collection<?> c)
{
for (Object e : c)
{
System.out.println(e);
}
} |
可以說Collection<?>是所有Collection的父類。
再來看一段下面的代碼
private void clearAllMaps(Collection<Map> c)
{
for(Map m:c)
{
m.clear();
}
} |
毫無疑問,它也存在上面我們所說的問題,也就是對(duì)HashMap之類Map的子類無法進(jìn)行操作,但是如果我們將參數(shù)改成Collection<?>又不大合理,因?yàn)槲覀冎幌M麑?duì)父類為Map的子類進(jìn)行操作,那么我們可以這樣改寫:
private void clearAllMaps(Collection<? extends Map> c)
{
for(Map m:c)
{
m.clear();
}
} |
類似于? extends Map之類的統(tǒng)配符稱為限定統(tǒng)配類型。
假設(shè)一個(gè)對(duì)象h類型為Collection<HashMap>,那么我們將h作為參數(shù)傳給clearAllMaps,如下面一段代碼所示:
List<HashMap<String,String>> h=new ArrayList<HashMap<String,String>>();
HashMap<String,String> m=new HashMap<String,String>();
m.put("key","value");
h.add(m);
clearAllMaps(h); |
對(duì)于在類似于上面所說,使用了? extend XXX的方法,值得注意的一點(diǎn)是不能夠在方法體內(nèi)用XXX的子類對(duì)象作為代替。如下面一段代碼是錯(cuò)誤的:
public void addRectangle(List<? extends Shape> shapes)
{
shapes.add(0, new Rectangle()); // 錯(cuò)誤用法!
} |
這里我們假設(shè)Rectangle是Shape的一個(gè)子類。
不允許這樣寫的原因比較簡單,因?yàn)檎{(diào)用該方法時(shí)候參數(shù)類型可能是Shape的另外一個(gè)子類。假如說Shape除了Rectangle這個(gè)子類以外還有另外一個(gè)子類Circle,那么我們可以把一個(gè)List<Circle>類型的對(duì)象作為參數(shù)傳給這個(gè)方法(注意這樣是合法的),而在方法體內(nèi)卻把一個(gè)Rectangle對(duì)象放到了shapes里面,這顯然是不合理的。
除了extends,在泛型參數(shù)類型中還可以使用super關(guān)鍵字,參照下面一段程序:
private void addString(Collection <? super String> c)
{
c.add("a String");
} |
泛型函數(shù)
我們?cè)谇懊嫣岬搅私y(tǒng)配類型,現(xiàn)在讓我們來設(shè)想一個(gè)函數(shù),它實(shí)現(xiàn)這樣的功能,將一個(gè)數(shù)組中的元素添加到一個(gè)Collection中,為了保證程序的通用性,我們可能會(huì)寫出另外一段錯(cuò)誤的代碼:
private void fromArrayToCollection(Object[] a, Collection<?> c)
{
for (Object o : a)
{
c.add(o); // 錯(cuò)誤的代碼
}
} |
那么這個(gè)函數(shù)應(yīng)該怎么寫呢?我們可以通過對(duì)函數(shù)添加泛型參數(shù)的方法實(shí)現(xiàn),如下面所示:
private <T> void exfromArrayToCollection(T[] a, Collection<T> c)
{
for (T o : a)
{
c.add(o); //這樣是正確的
}
} |
那么,在什么時(shí)候我們應(yīng)該使用統(tǒng)配類型,什么時(shí)候我們應(yīng)該使用泛型函數(shù)呢?答案是取決于函數(shù)參數(shù)之間,函數(shù)參數(shù)和返回值之間的類型依賴性。
如果一個(gè)函數(shù)的參數(shù)類型與函數(shù)返回的參數(shù)沒有必然關(guān)聯(lián),同時(shí)對(duì)于該函數(shù)其他的參數(shù)的類型也沒有依賴關(guān)系,那么我們就應(yīng)該使用統(tǒng)配符,否則就應(yīng)該使用泛型函數(shù)。
為了更清楚地說明這一點(diǎn),我們可以看一下java.util包中Collections類型幾個(gè)方法的定義:
class Collections {
static void swap(List<?> list, int i, int j) {...}
static <T> void copy (List<? super T> dest, List<? extends T> src) {...}
} |
其中swap函數(shù)實(shí)際上也可以這樣定義:
static <T>void swap(List<T> list, int i, int j) {...} |
但是注意到這里泛型類型參數(shù)T只在參數(shù)中用到了一次,也就是說它和函數(shù)其他部分沒有依賴性,這可以看作是我們應(yīng)該使用?的一個(gè)標(biāo)志。
copy方法中,拷貝源src中的元素必須是dest所能夠接受的,src中的元素必須是T的一個(gè)子類,但是具體它是哪種子類我們又不必關(guān)心,所以方法中使用了泛型作為一個(gè)類型參數(shù),同時(shí)也用了統(tǒng)配類型作為第二類型參數(shù)
posted on 2005-05-12 10:55
幻 閱讀(993)
評(píng)論(0) 編輯 收藏 所屬分類:
編程相關(guān)