java范型學(xué)習(xí)
泛型不用考慮對象的具體類型。優(yōu)點(diǎn)在于,因?yàn)椴挥每紤]對象的具體類型所以可以對一類對象執(zhí)行一定的相同操作;缺點(diǎn)在于,因?yàn)闆]有考慮對象的具體類型所以就不能使用對象自帶的接口函數(shù)。泛型的最佳用同是實(shí)現(xiàn)容器類。在java中,范型是在編譯器中實(shí)現(xiàn)的,而不是在虛擬機(jī)中實(shí)現(xiàn)的,虛擬機(jī)對范型一無所知。因此,編譯器一定要把范型類修改為普通類,才能夠在虛擬機(jī)中執(zhí)行。在java中,這種技術(shù)稱之為“擦除”,也就是用Object類型替換范型(Comparable來替換所有范型)。當(dāng)需要用到其他約束中定義的方法的時(shí)候,通過插入強(qiáng)制轉(zhuǎn)化代碼來實(shí)現(xiàn)。Wildcard支持另外一個關(guān)鍵字super,而范型方法不支持super關(guān)鍵字。下面是一個簡單的泛型類的代碼package demo;
public class Name<T> {
public Name() {
this.firstName = null;
this.secondName = null;
}
public Name(T firstName, T secondName) {
this.firstName = firstName;
this.secondName = secondName;
}
public T getFirstName() {
return firstName;
}
public void setFirstName(T firstName) {
this.firstName = firstName;
}
public T getSecondName() {
return secondName;
}
public void setSecondName(T secondName) {
this.secondName = secondName;
}
private T firstName;
private T secondName;
}
==================================================================
package demo;
public class Name {
public Name() {
this.firstName = null;
this.secondName = null;
}
public Name(Object firstName, Object secondName) {
this.firstName = firstName;
this.secondName = secondName;
}
public Object getFirstName() {
return firstName;
}
public void setFirstName(Object firstName) {
this.firstName = firstName;
}
public Object getSecondName() {
return secondName;
}
public void setSecondName(Object secondName) {
this.secondName = secondName;
}
private Object firstName;
private Object secondName;
}
每當(dāng)你用一個具體類去實(shí)例化該范型時(shí),編譯器都會在原生類的基礎(chǔ)上,通過強(qiáng)制約束和在需要的地方添加強(qiáng)制轉(zhuǎn)換代碼來滿足需求,但是不會生成更多的具體的類。
Pair<Employee> buddies = new Pair<Employee>();
在上述原生代碼中,此處參數(shù)類型是Object,理論上可以接納各種類型,但編譯器通過強(qiáng)制約束在此使用Employee(及子類)類型的參數(shù),其他類型編譯器一律報(bào)錯buddies.setFirst(new Employee("張三")); 在上述原生代碼中,getFirst()的返回值是一個Object類型,是不可以直接賦給類型為Employee的buddy的,但編譯器在此做了手腳,添加了強(qiáng)制轉(zhuǎn)化代碼,實(shí)際代碼應(yīng)該是Employee buddy = (Employee)buddies.getFirst();這樣就合法了。但編譯器做過手腳的代碼你是看不到的,他是以字節(jié)碼的形式完成的。Employee buddy = buddies.getFirst();一般情況下不要涉及類型的具體信息。范型類可以繼承自某一個父類,或者實(shí)現(xiàn)某個接口,或者同時(shí)繼承父類并且實(shí)現(xiàn)接口。這樣的話,就可以對類型調(diào)用父類或接口中定義的方法了。
public class Pair<T extends Comparable>
...{
public boolean setSecond(T newValue) ...{
boolean flag = false;
If(newValue.compareTo(first)>0) ...{
second = newValue;
flag = true;
}
return flag;
}
private T first;
private T second;
}
上面的范型T被添加了一個約束條件,那就是他必須實(shí)現(xiàn)Comparable接口,這樣的話,就可以對范型T使用接口中定義的方法了,也就可以實(shí)現(xiàn)2個元素大小的比較。為了簡化范型的設(shè)計(jì),無論是繼承類還是實(shí)現(xiàn)接口,一律使用extends關(guān)鍵字。若同時(shí)添加多個約束,各個約束之間用“&”分隔,比如:public class Pair<T extends Comparable & Serializable>。那么編譯器是如何處理這種情況呢?前面講過,范型類最終都會被轉(zhuǎn)化為原生類。在前面沒有添加約束的時(shí)候,編譯器將范型通通替換為Object;而增加了約束之后,通通用第一個約束來替換范型
ArrayList<Integer> l = new ArrayList<Integer>();
test(l); //此處編譯器會報(bào)錯??!
Integer確實(shí)是Number的子類,但是,ArrayList<Integer>并不是ArrayList<Number>的子類,二者之間沒有任何的繼承關(guān)系
public static void test(ArrayList<Number> l) ...{
l.add(new Float(2));
}
在函數(shù)內(nèi)部,我們把Float類型的元素插入到鏈表中。因?yàn)殒湵硎荖umber類型,這條語句沒問題。但是,如果實(shí)參是一個Integer類型的鏈表,他能存儲Float類型的數(shù)據(jù)嗎??顯然不能,這樣就會造成運(yùn)行時(shí)錯誤。于是,編譯器干脆就不允許進(jìn)行這樣的傳遞。在向容器類添加內(nèi)容的時(shí)候可能造成類型不匹配。
===================================================================
// 1.在定義方法的時(shí)候使用Wildcard(也就是下述代碼中的問號)。
public static void test1(ArrayList<? extends Number> l) ...{
Integer n = new Integer(45);
Number x = l.get(0); //從鏈表中取數(shù)據(jù)是允許的
l.add(n); //錯誤!!往鏈表里面插入數(shù)據(jù)是被編譯器嚴(yán)格禁止的??!
}
// 2.定義一個范型方法。代碼如下:
public static <T extends Number> void test2(ArrayList<T> l) ...{
Number n = l.get(0);
T d = l.get(0);
l.add(d); //與上面的方法相比,插入一個范型數(shù)據(jù)是被允許的,相對靈活一些
l.add(n); //錯誤??!只可以插入范型數(shù)據(jù),絕不可插入具體類型數(shù)據(jù)。
}
只要我們對形參添加了一定的約束條件,那么我們在傳遞實(shí)參的時(shí)候,對實(shí)參的嚴(yán)格約束就會降低一些。上述代碼都指定了一個類Number,并用了extends關(guān)鍵字,因此,在傳遞實(shí)參的時(shí)候,凡是從Number繼承的類組成的鏈表,均可以傳遞進(jìn)去。但上面代碼的注釋中也說的很清楚,為了不出現(xiàn)運(yùn)行時(shí)錯誤,編譯器會對你調(diào)用的方法做嚴(yán)格的限制:凡是參數(shù)為范型的方法,一律不需調(diào)用??!
public static void test5(ArrayList<? super Integer> l) ...{
Integer n = new Integer(45);
l.add(n); //與上面使用extends關(guān)鍵字相反,往鏈表里面插入指定類型的數(shù)據(jù)是被允許的。
Object x = l.get(0); //從鏈表里取出一個數(shù)據(jù)仍然是被允許的,不過要賦值給Object對象。
l.add(x); //錯誤??!將剛剛?cè)〕龅臄?shù)據(jù)再次插入鏈表是不被允許的。
}
對實(shí)參的限制更改為:必須是指定類型的父類。這里我們指定了Integer類,那么實(shí)參鏈表的元素類型,必須是Number類及其父類。下面我們重點(diǎn)討論一下上述代碼的第四條語句,為什么將剛剛?cè)〕龅臄?shù)據(jù)再次插入鏈表不被允許??道理很簡單,剛剛?cè)〕龅臄?shù)據(jù)被保存在一個Object類型的引用中,而鏈表的add方法只能接受指定類型Integer及其子類,類型不匹配當(dāng)然不行。
//幫助函數(shù)
public static <T>void helperTest5(ArrayList<T> l, int index) ...{
T temp = l.get(index);
l.add(temp);
}
//主功能函數(shù)
public static void test5(ArrayList<? super Integer> l) ...{
Integer n = new Integer(45);
l.add(n);
helperTest5(l, 0); //通過幫助類,將指定的元素取出后再插回去。
}
上述兩個函數(shù)結(jié)合的原理就是:利用Wildcard的super關(guān)鍵字來限制參數(shù)的類型(范型函數(shù)不支持super,要是支持的話就不用這么麻煩了),然后通過范型函數(shù)來完成取出數(shù)據(jù)的再存儲。注意:
//1、不可以用一個本地類型(如int float)來替換范型
//2、運(yùn)行時(shí)類型檢查,不同類型的范型類是等價(jià)的(Pair<String>與Pair<Employee>是屬于同一個類型Pair),
// 這一點(diǎn)要特別注意,即如果a instanceof Pair<String>==true的話,并不代表a.getFirst()的返回值是一個String類型
//3、范型類不可以繼承Exception類,即范型類不可以作為異常被拋出
//4、不可以定義范型數(shù)組
//5、不可以用范型構(gòu)造對象,即first = new T(); 是錯誤的
//6、在static方法中不可以使用范型,范型變量也不可以用static關(guān)鍵字來修飾
//7、不要在范型類中定義equals(T x)這類方法,因?yàn)镺bject類中也有equals方法,當(dāng)范型類被擦除后,這兩個方法會沖突
//8、根據(jù)同一個范型類衍生出來的多個類之間沒有任何關(guān)系,不可以互相賦值
// 即Pair<Number> p1; Pair<Integer> p2; p1=p2; 這種賦值是錯誤的。
//9、若某個范型類還有同名的非范型類,不要混合使用,堅(jiān)持使用范型類
// Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo);
// Pair rawBuddies = managerBuddies; 這里編譯器不會報(bào)錯,但存在著嚴(yán)重的運(yùn)行時(shí)錯誤隱患
柳德才
13691193654
18942949207
QQ:422157370
liudecai_zan@126.com
湖北-武漢-江夏-廟山
posted on 2009-04-16 23:31 liudecai_zan@126.com 閱讀(1110) 評論(0) 編輯 收藏 所屬分類: 程序人生