目錄:
Java語法糖系列一:可變長度參數(shù)和foreach循環(huán)
http://www.reibang.com/p/628568f94ef8
Java語法糖系列二:自動(dòng)裝箱/拆箱和條件編譯
http://www.reibang.com/p/946b3c4a5db6
Java語法糖系列三:泛型與類型擦除
http://www.reibang.com/p/4de08deb6ba4
Java語法糖系列四:枚舉類型
http://www.reibang.com/p/ae09363fe734
Java語法糖系列五:內(nèi)部類和閉包
http://www.reibang.com/p/f55b11a4cec2
前兩篇寫到可變長參數(shù)工育、foreach循環(huán)泪姨、自動(dòng)裝箱/拆箱和條件編譯奶栖,這篇討論下java的泛型與類型擦除棘脐。
泛型與類型擦除
泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用棉姐,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)工碾。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中踪央,分別稱為泛型類臀玄、泛型接口和泛型方法。
泛型思想早在C++語言的模板(Templates)中就開始生根發(fā)芽畅蹂,在Java語言處于還沒有出現(xiàn)泛型的版本時(shí)健无,只能通過Object是所有類型的父類和類型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來實(shí)現(xiàn)類型泛化。
例如在哈希表的存取中液斜,JDK 1.5之前使用HashMap的get()方法累贤,返回值就是一個(gè)Object對(duì)象叠穆,由于Java語言里面所有的類型都繼承于java.lang.Object,那Object轉(zhuǎn)型成任何對(duì)象都是有可能的臼膏。但是也因?yàn)橛袩o限的可能性硼被,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類型的對(duì)象。在編譯期間渗磅,編譯器無法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功嚷硫,如果僅僅依賴程序員去保障這項(xiàng)操作的正確性,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)被轉(zhuǎn)嫁到程序運(yùn)行期之中始鱼。
泛型技術(shù)在C#和Java之中的使用方式看似相同仔掸,但實(shí)現(xiàn)上卻有著根本性的分歧,C#里面泛型無論在程序源碼中风响、編譯后的IL中(Intermediate Language嘉汰,中間語言,這時(shí)候泛型是一個(gè)占位符)或是運(yùn)行期的CLR中都是切實(shí)存在的状勤,List<int>與List<String>
就是兩個(gè)不同的類型鞋怀,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類型數(shù)據(jù)持搜,這種實(shí)現(xiàn)稱為類型膨脹密似,基于這種方法實(shí)現(xiàn)的泛型被稱為真實(shí)泛型。
Java語言中的泛型則不一樣葫盼,它只在程序源碼中存在残腌,在編譯后的字節(jié)碼文件中,就已經(jīng)被替換為原來的原生類型(Raw Type贫导,也稱為裸類型)了抛猫,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼。
先看個(gè)例子
public static void main(String[] args){
List <Integer> listInt=new ArrayList <Integer>();
List <String> listString=new ArrayList <String>();
Map<String, String> map = new HashMap<String, String>();
map.put("AAA", "BBB");
map.put("CCC", "DDD");
System.out.print(map.get("AAA"));
}
編譯出來的代碼
public static void main(String[] paramArrayOfString)
{
ArrayList localArrayList1 = new ArrayList();
ArrayList localArrayList2 = new ArrayList();
HashMap localHashMap = new HashMap();
localHashMap.put("AAA", "BBB");
localHashMap.put("CCC", "DDD");
System.out.print((String)localHashMap.get("AAA"));
}
因此對(duì)于運(yùn)行期的Java語言來說孩灯,ArrayList <Integer>與ArrayList<String>
編譯出來的代碼是一樣的闺金,所以說泛型技術(shù)實(shí)際上是Java語言的一顆語法糖,Java語言中的泛型實(shí)現(xiàn)方法稱為類型擦除峰档,基于這種方法實(shí)現(xiàn)的泛型被稱為偽泛型败匹。
泛型與重載
首先看看重載(Overloading)的定義:
Java的方法重載,就是在類中可以創(chuàng)建多個(gè)方法讥巡,它們具有相同的名字掀亩,但具有不同的參數(shù)和不同的定義。調(diào)用方法時(shí)通過傳遞給它們的不同參數(shù)個(gè)數(shù)和參數(shù)類型來決定具體使用哪個(gè)方法, 這就是多態(tài)性欢顷。
重載的時(shí)候槽棍,方法名要一樣,但是參數(shù)類型和個(gè)數(shù)不一樣,返回值類型可以相同也可以不相同刹泄。
無法以返回型別作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)外里。
上面也說了,泛型編譯出來的代碼是會(huì)把類型擦除的特石,所以如下的代碼是不能編譯的盅蝗,是因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>姆蘸,擦除動(dòng)作導(dǎo)致這兩個(gè)方法的特征簽名變得一模一樣墩莫,或者說兩個(gè)一模一樣的方法不能共存在一個(gè)class文件里。
public void method(List<String> list) {
System.out.println(list.get(0));
}
public void method(List<Integer> list) {
System.out.println(list.get(0));
}
那么如果加上返回類型呢逞敷?
public String method(List<String> list) {
System.out.println(list.get(0));
return "";
}
public int method(List<Integer> list) {
System.out.println(list.get(0));
return 1;
}
在eclipse 上仍然報(bào)錯(cuò)狂秦,參考重載定義的第三點(diǎn)無法以返回型別作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)
一點(diǎn)拓展
上面加上了返回類型的兩個(gè)方法,在eclipse上編譯不通過推捐,但在Javac編譯器中是可以編譯的裂问,編譯出來的代碼如下:
public String method(List<String> paramList)
{
System.out.println((String)paramList.get(0));
return "";
}
public int method(List<Integer> paramList) {
System.out.println(paramList.get(0));
return 1;
}
那是不是說明重載可以以返回類型作區(qū)分呢?不是的牛柒。因?yàn)橄褚韵逻@樣的代碼用javac也編譯不了
`public String method(String list) {
System.out.println(list);
return "";
}
public int method(String list) {
System.out.println(list);
return 1;
} `
網(wǎng)上找到一段引用:
在《Java虛擬機(jī)規(guī)范第二版》(JDK 1.5修改后的版本)的“§4.4.4 Signatures”章節(jié)及《Java語言規(guī)范第三版》的“§8.4.2 Method Signature”章節(jié)中分別都定義了字節(jié)碼層面的方法特征簽名堪簿,以及Java代碼層面的方法特征簽名,特征簽名最重要的任務(wù)就是作為方法獨(dú)一無二不可重復(fù)的ID皮壁,在Java代碼中的方法特征簽名只包括了方法名稱椭更、參數(shù)順序及參數(shù)類型,而在字節(jié)碼中的特征簽名還包括方法返回值及受查異常表蛾魄。
根據(jù)上面的例子說明:由于List<String>和List<Integer>
擦除后是同一個(gè)類型虑瀑,只能添加兩個(gè)并不需要實(shí)際使用到的返回值才能完成重載。這是否是一種引入泛型后的折中的解決方案呢滴须?
最后舌狗,通過反射依然能獲取到參數(shù)化的類型,說明擦除法所謂的擦除扔水,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除痛侍,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息。
測(cè)試如圖: