JAVA泛型和C++泛型的區(qū)別:
創(chuàng)新互聯(lián)長期為超過千家客戶提供的網(wǎng)站建設服務,團隊從業(yè)經(jīng)驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為黃巖企業(yè)提供專業(yè)的成都做網(wǎng)站、成都網(wǎng)站建設,黃巖網(wǎng)站改版等技術服務。擁有十余年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
Java的泛型被定義成擦除,而C++的泛型則是擴展;
對于C++模板,當參數(shù)類型為不同的類型時,生成的模板實例也是不同的類型,如:定義類模板
Template
當實例化模板時
A
A
這里的a和b是兩種不同的類型的實例;
Java則不是這樣的,如泛化List
List
List
通過反射機制看l和s的class,他們都是List!所有的參數(shù)類型都被編譯器擦除了!
這樣造成的結果是以往用C++模板可以實現(xiàn)某種契約(contract)的功能在Java中變得很另類了,舉一個例子:
C++代碼:
Template
Private:
T x;
Public:
Void func(){
int ret = x.foo(12);
}
}
上面這段代碼在C++中經(jīng)常能夠看到,它暗中就設定了一個契約:凡是能否實例化這個模板的類型T,其必須具有一個公共的函數(shù)foo,這個函數(shù)返回整數(shù),并且接受一個整數(shù)做為參數(shù)。
Java的代碼:
public class A
private E e;
public void func(){
int ret = e.foo(12); //編譯錯誤啊…
}
}
編譯器給出的錯誤原因是foo函數(shù)對于類型中E是未定義的??!造成這樣的問題的原因有兩個:
1、C++的編譯器直到模板被使用(實例化)的時候才去編譯模板,如果你不去使用模板C++編譯器不會編譯模板;而實例化的時候編譯器已經(jīng)能夠確定具體的參數(shù)類型,所以能夠檢測契約是否符合;Java的編譯器不是這樣工作的,所以它在編譯模板類型的時候不能夠確定E到底有沒有這個foo函數(shù);
2、類型擦除的結果;修改一下上面的程序,我們看看在func中到底能夠調用什么函數(shù),一看只能調用Object對象中的函數(shù)。所有的類型E都被擦除成Object了!
如果真的要想實現(xiàn)類似C++的契約,就必須確保參數(shù)類型E不被擦除成Object!需要如下修改代碼:
public class A
private E e;
public void func(){
int ret = e.foo(12);
}
}
Class B{
Public int foo(int param){…};
}
這樣雖然可以實現(xiàn)我們期望的形式,但是約束的程度要比C++的強很多,C++中,只要任意類型,其具有一個符合契約的函數(shù),就可以實例化模板,而Java中,則要求所有的類型必須是給定類型的子類才可以實例化模板;
擦除的原則:
1、所有參數(shù)化容器類都被擦除成非參數(shù)化的(raw type);如List>都被擦除成List;
2、所有參數(shù)化數(shù)組都被擦除成非參數(shù)化的數(shù)組;如List
3、Raw type的容器類,被擦除成其自身,如List被擦除成List;
4、原生類型(int,String還有wrapper類)都擦除成他們的自身;
5、參數(shù)類型E,被擦除成Object;
6、所有約束參數(shù)如 Extends E>、
7、如果有多個約束,擦除成第一個,如
例如:
泛化代碼:
List words.add("Hello ");
words.add("world!");
String s = words.get(0)+words.get(1);
擦除后就變成了:
List words = new ArrayList();
words.add("Hello ");
words.add("world!");
String s = (擦除后的代碼和以前沒有泛型時候寫的代碼沒有任何區(qū)別!
再例如:
泛化代碼:
publicclass textReader
privateTa;
public textReader(T b){
this.a = b;
}
publicT getA(){
returna;
}
publicstaticvoid main(String[] agrvs){
String in ="1234567890";
textReader
String out = test.getA();
System.out.println(out);
}
}
擦除后(所有類型參數(shù)都被去掉,T被擦除成Object)就變成(注意紅色部分):
publicclass textReader{
privateObjecta;
public textReader(Object b){
this.a = b;
}
publicObject getA(){
returna;
}
publicstaticvoid main(String[] agrvs){
String in ="1234567890";
textReader test =new textReader (in);
String out = (String)test.getA();
System.out.println(out);
}
}
擦除所帶來的問題:
1、靜態(tài)成員共享問題
Listints = Arrays.asList(1,2,3);
Liststrings = Arrays.asList("one","two");
assert ints.getClass() == strings.getClass();
ints和strings兩個對象最終被擦除成具有相同類型的(List)的對象,于是這兩個對象共享List的靜態(tài)成員,于是就可以得出這樣的結論,所有泛化類型的靜態(tài)成員被其所有的實例化對象共享,因此也就要求所有靜態(tài)成員不能夠是泛化的!
class Foo{
private final T value;
private public T getValue() { return value; }
public 2、過載(overload)沖突問題函數(shù)過載的定義這樣的:在一個類的范圍內(nèi),如果兩個函數(shù)具有相同的函數(shù)名稱,不同的參數(shù)(返回值不考慮)就互相稱為過載函數(shù)??匆粋€例子:
Class A{
Public int foo(int a){};
Public int foo(float f){}; //是過載,編譯沒有問題
Public int foo(int a){};
Public float foo(int f){}; //報錯
Public static int foo1(List
a){} Public static int foo1(List
s){} //編譯有錯誤,因為所有的List 都被擦除成List,這樣兩個函數(shù)重復定義,報錯; Public static int foo1(List
a){} Public static String foo1(List
s){} //沒有問題,編譯器不會報錯! }
3、接口實現(xiàn)
一個類不能同時實現(xiàn)具有相同擦除效果的接口,例如:
class Foo implements Comparable
, Comparable 繼承關系:
1、原始繼承:就是我們經(jīng)常提到的繼承關系,如ArrayList是List的子類;
2、泛化繼承:
a)泛化后的ArrayList
依舊是List 的子類;其中T是參數(shù)化類型 b)如果類型T是類型B的子類,那么List
不是List的子類 c)List
是List extends T>的子類 d)List
是List extends B>的子類 e)List
是List super T>的子類 f)List是List super T>的子類
g)如果類型T是類型B的子類,那么T[]是B[]的子類
3、關于協(xié)變式(covariant)、不變式(invariant)和反變式(contravariant):
a)數(shù)組和擴展類(extends)的泛化是協(xié)變式,即如果類型T是類型B的子類,那么T[]是B[]的子類;List
是List extends B>的子類 b)非擴展類泛型是不變式,即如果類型T是類型B的子類,那么List
不是List的子類 c)Super類泛型是反變式,即如果B是T的超類,則List是List super T>的子類
Get和Put原則:
當從一個泛化的結構中取數(shù)據(jù)的時候請使用extends通配,當往一個泛化的結構中放數(shù)據(jù)的時候請使用super通配;
當需要同時從一個泛化結構中讀取和寫入數(shù)據(jù)是,請不使用通配符號;
為什么會這樣,我們簡單的分析一下:假定類型T繼承自A和B,類型C和D又從T類型繼承,那么List extends T>中存放的只能是T類型或是C或是D類型,里面存放的類型都可以向上cast到T,所以從List extends T>中取東西編譯器能夠正確處理,只要映射到T就可以了(T是他們的父類)!往List extends T>放東西就不一樣的,原來里面放的是T/C還是D都被擦除成T,所以編譯器不知道原來到底存放的是什么類型,無法保證類型安全,所以這個操作被禁止!
List super T>中存放的是A/B或是T,往List super T>放T是允許的,因為T總是可以向上轉換成A或是B,但是從里面取東西就有問題了,編譯器還是不能夠確定List里面放的是什么類型,可能是A也可能是B。
具體化:
當一個類型能夠在運行時態(tài)被完整的表現(xiàn),我們就稱為其是可以具體化的,舉一個例子:
List
就不是一個可以具體化的類型,因為它在運行時態(tài)被擦除成List!所以當處理一些需要運行時檢測類型的操作的時候(如instanceof)就要特別注意。 究竟哪些類型是可具體化的呢;
(1)、原始類型,如int;
(2)、非參數(shù)化的類和接口;
(3)、非限定的參數(shù)化類型,如List>, Map,?>
(4)、Raw類型,如 List,ArrayList等
(5)、所有由可具體化類型組成的數(shù)組,如Number[],List>[]等
哪些是不可具體化的類型呢
(1)、類型變量,如T;
(2),參數(shù)化類型,如List
等 (3),有限定的參數(shù)化類型,如List extends Number>;
具體哪些操作需要注意區(qū)分是否是具體化類型:
當前標題:JAVA泛型淺析
路徑分享:http://weahome.cn/article/podpoi.html