1. 程式人生 > >「深入Java虛擬機(6)」:Java語法糖

「深入Java虛擬機(6)」:Java語法糖

拆裝箱 重載 jdk1 支持 名稱 不存在 語法糖 pub 簽名

語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家Peter.J.Landin發明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便程序員使用。Java中最常用的語法糖主要有泛型、變長參數、條件編譯、自動拆裝箱、內部類等。虛擬機並不支持這些語法,它們在編譯階段就被還原回了簡單的基礎語法結構,這個過程成為解語法糖。

泛型是JDK1.5之後引入的一項新特性,Java語言在還沒有出現泛型時,只能通過Object是所有類型的父類和類型強制轉換這兩個特點的配合來實現泛型的功能,這樣實現的泛型功能要在程序運行期才能知道Object真正的對象類型,在Javac編譯期,編譯器無法檢查這個Object的強制轉型是否成功,這便將一些風險轉接到了程序運行期中。

Java語言在JDK1.5之後引入的泛型實際上只在程序源碼中存在,在編譯後的字節碼文件中,就已經被替換為了原來的原生類型,並且在相應的地方插入了強制轉型代碼,因此對於運行期的Java語言來說,ArrayList<String>和ArrayList<Integer>就是同一個類。所以泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基於這種方法實現的泛型被稱為偽泛型。

下面是一段簡單的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文件,然後再用字節碼反編譯工具進行反編譯後,將會發現泛型都變回了原生類型,如下面的代碼所示:

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));
為了更詳細地說明類型擦除,再看如下代碼:

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");
}
}
當我用Javac編譯器編譯這段代碼時,報出了如下錯誤:

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 錯誤

這是因為泛型List<String>和List<Integer>編譯後都被擦除了,變成了一樣的原生類型List,擦除動作導致這兩個方法的特征簽名變得一模一樣,在Class類文件結構一文中講過,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;
}
}
發現這時編譯可以通過了(註意:Java語言中true和1沒有關聯,二者屬於不同的類型,不能相互轉換,不存在C語言中整數值非零即真的情況)。兩個不同類型的返回值的加入,使得方法的重載成功了。這是為什麽呢?

我們知道,Java代碼中的方法特征簽名只包括了方法名稱、參數順序和參數類型,並不包括方法的返回值,因此方法的返回值並不參與重載方法的選擇,這樣看來為重載方法加入返回值貌似是多余的。對於重載方法的選擇來說,這確實是多余的,但我們現在要解決的問題是讓上述代碼能通過編譯,讓兩個重載方法能夠合理地共存於同一個Class文件之中,這就要看字節碼的方法特征簽名,它不僅包括了Java代碼中方法特征簽名中所包含的那些信息,還包括方法返回值及受查異常表。為兩個重載方法加入不同的返回值後,因為有了不同的字節碼特征簽名,它們便可以共存於一個Class文件之中。

自動拆裝箱、變長參數等語法糖也都是在編譯階段就把它們該語法糖結構還原為了原生的語法結構,因此在Class文件中也只存在其對應的原生類型,這裏不再一一說明。

「深入Java虛擬機(6)」:Java語法糖