泛型不用考慮對象的具體類型。優(yōu)點在于,因為不用考慮對象的具體類型所以可以對一類對象執(zhí)行一定的相同操作;缺點在于,因為沒有考慮對象的具體類型所以就不能使用對象自帶的接口函數(shù)。泛型的最佳用同是實現(xiàn)容器類。在java中,范型是在編譯器中實現(xiàn)的,而不是在虛擬機中實現(xiàn)的,虛擬機對范型一無所知。因此,編譯器一定要把范型類修改為普通類,才能夠在虛擬機中執(zhí)行。在java中,這種技術(shù)稱之為“擦除”,也就是用Object類型替換范型(Comparable來替換所有范型)。當需要用到其他約束中定義的方法的時候,通過插入強制轉(zhuǎn)化代碼來實現(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;
}
每當你用一個具體類去實例化該范型時,編譯器都會在原生類的基礎(chǔ)上,通過強制約束和在需要的地方添加強制轉(zhuǎn)換代碼來滿足需求,但是不會生成更多的具體的類。
Pair<Employee> buddies = new Pair<Employee>();
在上述原生代碼中,此處參數(shù)類型是Object,理論上可以接納各種類型,但編譯器通過強制約束在此使用Employee(及子類)類型的參數(shù),其他類型編譯器一律報錯buddies.setFirst(new Employee("張三")); 在上述原生代碼中,getFirst()的返回值是一個Object類型,是不可以直接賦給類型為Employee的buddy的,但編譯器在此做了手腳,添加了強制轉(zhuǎn)化代碼,實際代碼應該是Employee buddy = (Employee)buddies.getFirst();這樣就合法了。但編譯器做過手腳的代碼你是看不到的,他是以字節(jié)碼的形式完成的。Employee buddy = buddies.getFirst();一般情況下不要涉及類型的具體信息。范型類可以繼承自某一個父類,或者實現(xiàn)某個接口,或者同時繼承父類并且實現(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被添加了一個約束條件,那就是他必須實現(xiàn)Comparable接口,這樣的話,就可以對范型T使用接口中定義的方法了,也就可以實現(xiàn)2個元素大小的比較。為了簡化范型的設(shè)計,無論是繼承類還是實現(xiàn)接口,一律使用extends關(guān)鍵字。若同時添加多個約束,各個約束之間用“&”分隔,比如:public class Pair<T extends Comparable & Serializable>。那么編譯器是如何處理這種情況呢?前面講過,范型類最終都會被轉(zhuǎn)化為原生類。在前面沒有添加約束的時候,編譯器將范型通通替換為Object;而增加了約束之后,通通用第一個約束來替換范型
ArrayList<Integer> l = new ArrayList<Integer>();
test(l); //此處編譯器會報錯!!
Integer確實是Number的子類,但是,ArrayList<Integer>并不是ArrayList<Number>的子類,二者之間沒有任何的繼承關(guān)系
public static void test(ArrayList<Number> l) ...{
l.add(new Float(2));
}
在函數(shù)內(nèi)部,我們把Float類型的元素插入到鏈表中。因為鏈表是Number類型,這條語句沒問題。但是,如果實參是一個Integer類型的鏈表,他能存儲Float類型的數(shù)據(jù)嗎??顯然不能,這樣就會造成運行時錯誤。于是,編譯器干脆就不允許進行這樣的傳遞。在向容器類添加內(nèi)容的時候可能造成類型不匹配。
===================================================================
// 1.在定義方法的時候使用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ù)是被編譯器嚴格禁止的!!
}
// 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ù)。
}
只要我們對形參添加了一定的約束條件,那么我們在傳遞實參的時候,對實參的嚴格約束就會降低一些。上述代碼都指定了一個類Number,并用了extends關(guān)鍵字,因此,在傳遞實參的時候,凡是從Number繼承的類組成的鏈表,均可以傳遞進去。但上面代碼的注釋中也說的很清楚,為了不出現(xiàn)運行時錯誤,編譯器會對你調(diào)用的方法做嚴格的限制:凡是參數(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ù)再次插入鏈表是不被允許的。
}
對實參的限制更改為:必須是指定類型的父類。這里我們指定了Integer類,那么實參鏈表的元素類型,必須是Number類及其父類。下面我們重點討論一下上述代碼的第四條語句,為什么將剛剛?cè)〕龅臄?shù)據(jù)再次插入鏈表不被允許??道理很簡單,剛剛?cè)〕龅臄?shù)據(jù)被保存在一個Object類型的引用中,而鏈表的add方法只能接受指定類型Integer及其子類,類型不匹配當然不行。
//幫助函數(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、運行時類型檢查,不同類型的范型類是等價的(Pair<String>與Pair<Employee>是屬于同一個類型Pair),
// 這一點要特別注意,即如果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)這類方法,因為Object類中也有equals方法,當范型類被擦除后,這兩個方法會沖突
//8、根據(jù)同一個范型類衍生出來的多個類之間沒有任何關(guān)系,不可以互相賦值
// 即Pair<Number> p1; Pair<Integer> p2; p1=p2; 這種賦值是錯誤的。
//9、若某個范型類還有同名的非范型類,不要混合使用,堅持使用范型類
// Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo);
// Pair rawBuddies = managerBuddies; 這里編譯器不會報錯,但存在著嚴重的運行時錯誤隱患
柳德才
13691193654
18942949207
QQ:422157370
liudecai_zan@126.com湖北-武漢-江夏-廟山