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

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

語法糖(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檔案中也只存在其對應的原生型別,這裡不再一一說明。