語(yǔ)法糖(Syntactic Sugar),也稱糖衣語(yǔ)法,是由英國(guó)計(jì)算機(jī)學(xué)家 Peter.J.Landin 發(fā)明的一個(gè)術(shù)語(yǔ),指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響,但是更方便程序員使用。Java 中最常用的語(yǔ)法糖主要有泛型、變長(zhǎng)參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類等。虛擬機(jī)并不支持這些語(yǔ)法,它們?cè)诰幾g階段就被還原回了簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程成為解語(yǔ)法糖。
泛型是 JDK1.5 之后引入的一項(xiàng)新特性,Java 語(yǔ)言在還沒(méi)有出現(xiàn)泛型時(shí),只能通過(guò) Object 是所有類型的父類和類型強(qiáng)制轉(zhuǎn)換這兩個(gè)特點(diǎn)的配合來(lái)實(shí)現(xiàn)泛型的功能,這樣實(shí)現(xiàn)的泛型功能要在程序運(yùn)行期才能知道 Object 真正的對(duì)象類型,在 javac 編譯期,編譯器無(wú)法檢查這個(gè) Object 的強(qiáng)制轉(zhuǎn)型是否成功,這便將一些風(fēng)險(xiǎn)轉(zhuǎn)接到了程序運(yùn)行期中。
Java 語(yǔ)言在 JDK1.5 之后引入的泛型實(shí)際上只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)被替換為了原來(lái)的原生類型,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō),ArrayList和 ArrayList就是同一個(gè)類。所以泛型技術(shù)實(shí)際上是 Java 語(yǔ)言的一顆語(yǔ)法糖,Java 語(yǔ)言中的泛型實(shí)現(xiàn)方法稱為類型擦除,基于這種方法實(shí)現(xiàn)的泛型被稱為偽泛型。
下面是一段簡(jiǎn)單的 Java 泛型代碼:
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println(map.get(1));
System.out.println(map.get(2));
將這段 Java 代碼編譯成 Class 文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都變回了原生類型,如下面的代碼所示:
Map map = new HashMap();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println((String)map.get(1));
System.out.println((String)map.get(2));
為了更詳細(xì)地說(shuō)明類型擦除,再看如下代碼:
import java.util.List;
public class FanxingTest{
public void method(List<String> list){
System.out.println("List String");
}
public void method(List<Integer> list){
System.out.println("List Int");
}
}
當(dāng)用 javac 編譯器編譯這段代碼時(shí),報(bào)出了如下錯(cuò)誤:
FanxingTest.java:3: 名稱沖突:method(java.util.List<java.lang.String>) 和 method
(java.util.List<java.lang.Integer>) 具有相同疑符
public void method(List<String> list){
^
FanxingTest.java:6: 名稱沖突:method(java.util.List<java.lang.Integer>) 和 metho
d(java.util.List<java.lang.String>) 具有相同疑符
public void method(List<Integer> list){
^
2 錯(cuò)誤
是因?yàn)榉盒?List 和 List 編譯后都被擦除了,變成了一樣的原生類型 List,擦除動(dòng)作導(dǎo)致這兩個(gè)方法的特征簽名變得一模一樣,在 Class 類文件結(jié)構(gòu)一文中講過(guò),Class 文件中不能存在特征簽名相同的方法。
把以上代碼修改如下:
import java.util.List;
public class FanxingTest{
public int method(List<String> list){
System.out.println("List String");
return 1;
}
public boolean method(List<Integer> list){
System.out.println("List Int");
return true;
}
}
發(fā)現(xiàn)這時(shí)編譯可以通過(guò)了(注意:Java 語(yǔ)言中 true 和 1 沒(méi)有關(guān)聯(lián),二者屬于不同的類型,不能相互轉(zhuǎn)換,不存在 C 語(yǔ)言中整數(shù)值非零即真的情況)。兩個(gè)不同類型的返回值的加入,使得方法的重載成功了。這是為什么呢?
我們知道,Java 代碼中的方法特征簽名只包括了方法名稱、參數(shù)順序和參數(shù)類型,并不包括方法的返回值,因此方法的返回值并不參與重載方法的選擇,這樣看來(lái)為重載方法加入返回值貌似是多余的。對(duì)于重載方法的選擇來(lái)說(shuō),這確實(shí)是多余的,但我們現(xiàn)在要解決的問(wèn)題是讓上述代碼能通過(guò)編譯,讓兩個(gè)重載方法能夠合理地共存于同一個(gè) Class 文件之中,這就要看字節(jié)碼的方法特征簽名,它不僅包括了 Java 代碼中方法特征簽名中所包含的那些信息,還包括方法返回值及受查異常表。為兩個(gè)重載方法加入不同的返回值后,因?yàn)橛辛瞬煌淖止?jié)碼特征簽名,它們便可以共存于一個(gè) Class 文件之中。
自動(dòng)拆裝箱、變長(zhǎng)參數(shù)等語(yǔ)法糖也都是在編譯階段就把它們?cè)撜Z(yǔ)法糖結(jié)構(gòu)還原為了原生的語(yǔ)法結(jié)構(gòu),因此在 Class 文件中也只存在其對(duì)應(yīng)的原生類型,這里不再一一說(shuō)明。
更多建議: