《深刻理解Java虛擬機》--語法糖

語法糖簡述 

定義:

語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機科學家彼得·約翰·蘭達發明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能並無影響,可是更方便程序員使用。
java

Java語法糖的味道:

幾乎各類語言或多或少都提供過一些語法糖來方便程序員的代碼開發,這些語法糖雖然不會提供實質性的功能改進,可是它們或能提升效率,或能提高語法的嚴謹性,或能減小編碼出錯的機會。不過也有一種觀點認爲語法糖並不必定都是有益的,大量添加和使用「含糖」的語法,容易讓程序員產生依賴,沒法看清語法糖的糖衣背後,程序代碼的真實面目。
程序員

解語法糖:

Java在現代編程語言之中屬於「低糖語言」(相對於C#及許多其餘JVM語言來講),尤爲是JDK 1.5以前的版本。Java中最經常使用的語法糖主要是前面提到過的泛型(泛型並不必定都是語法糖實現,如C#的泛型就是直接由CLR支持的)、變長參數、自動裝箱/拆箱,遍歷循環(Foreach循環)等,虛擬機運行時不支持這些語法,它們在編譯階段還原回簡單的基礎語法結構,這個過程稱爲解語法糖。
編程


泛型與類型擦除

泛型是JDK 1.5的一項新增特性,它的本質是參數化類型(Parametersized Type)的應用,也就是說所操做的數據類型被指定爲一個參數。這種參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類、泛型接口和泛型方法。數組

最先期的泛型應用:

泛型思想早在C++語言的模板(Template)中就開始生根發芽,在Java語言處於尚未 出現泛型的版本時,只能經過Object是全部類型的父類和類型強制轉換兩個特色的配合來實現類型泛化。bash


缺點:

JDK 1.5以前使用HashMap的get()方法,返回值 就是一個Object對象,因爲Java語言裏面全部的類型都繼承於java.lang.Object,因此Object轉 型成任何對象都是有可能的。可是也由於有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object究竟是個什麼類型的對象。在編譯期間,編譯器沒法檢查這個Object的強制轉型是否成功,若是僅僅依賴程序員去保障這項操做的正確性,許多ClassCastException的風險就會轉嫁到程序運行期之中。編程語言


泛型技術:

C#裏面 泛型不管在程序源碼中、編譯後的IL中,或是運行期的CLR中,都是切實存在的,List<int>與List<String>就是兩個不一樣的類型,它們在系統運行期生成,有本身的虛方法表和類型數據,這種實現稱爲類型膨脹,基於這種方法實現的泛型稱爲真實泛型。ui

Java語言中的泛型則不同,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型(Raw Type,也稱爲裸類型)了,而且在相應的地方插入了強制轉型代碼,所以,對於運行期的Java語言來講,ArrayList<int>與ArrayList<String>就是同一 個類,因此泛型技術其實是Java語言的一顆語法糖,Java語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型稱爲僞泛型編碼


僞泛型的缺點:

當泛型碰見重載spa

public class GenericTypes{
    public static void method(List<String> list){
        System.out.println("invoke method(List<String>)");
    }
     public static void method(List<Integer> list){
        System.out.println("invoke method(List<Integer>)");
    }
}複製代碼

這段代碼是不能被編譯的,由於參數List<Integer>和List<String>編譯以後都被擦除了,變成了同樣的原生類型List<E>,擦除動做致使這兩種方法的特徵簽名變得如出一轍。
code


自動裝箱、拆箱與遍歷循環

// 編譯前
public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    // 若是在JDK1.7中,還有另外的一刻語法糖
    // 能讓上面這句代碼進一步簡寫成List<Integer> list = [1,2,3,4];
    int sum = 0;
    for (int i : list) {
        sum += i;
    }
    System.out.println(sum);
}複製代碼

代碼清單中一共包含了泛型、自動裝箱、自動拆箱、遍歷循環與變長參數5種語法糖。

// 編譯後
public static void main(String[] args) {
    List list = Arrays.asList(
        new Integer[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)});    
    int sum = 0;
    for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) {
        int i = ((Integer) localIterator.next()).intValue();
        sum += i;
    }
    System.out.println(sum);
}複製代碼

代碼清單則展現了它們在編譯後的變化。

泛型就沒必要說了,自動裝箱、拆箱在編譯以後被轉化成了對應的包裝和還原方法,如本例中的Integer.valueOf()與Integer.intValue()方法,而遍歷循環則把代碼還原成了迭代器的實現,這也是爲什麼遍歷循環須要被遍歷的類實現Iterable接口的緣由。最後再看看變長參數,它在調用的時候變成了一個數組類型的參數,在變長參數出現以前,程序員就是使用數組來完成相似功能的。


自動裝箱的陷阱:

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    int d = 3;
    Integer e = 127;
    Integer f = 127;
    Integer e1 = 321;
    Integer f1 =321;
    Long g = 3L;
    System.out.println(c.equals(d));         // true
    System.out.println(c == d);              // true
    System.out.println(c == (a + b));        // true
    System.out.println(e == f);              // true
    System.out.println(e1 == f1);            // false
    System.out.println(e1.equals(f1));       // true
    System.out.println(c.equals(a + b));     // true
    System.out.println(g == (a + b));        // true
    System.out.println(g.equals(a + b));     // false
}複製代碼

包裝類的「==」運算在不遇到算術運算的狀況下不會自動拆箱,以及它們equals()方法不處理數據轉型的關係。

JVM會自動維護八種基本類型的常量池,int常量池中初始化-128~127的範圍,因此當爲Integer i=127時,在自動裝箱過程當中是取自常量池中的數值,而當Integer i=128時,128不在常量池範圍內,因此在自動裝箱過程當中需new 128,因此地址不同。對於Integer來講,你用==比較的是對象引用地址,而不是Integer的值。Integer你要把噹噹成一個對象來看待

相關文章
相關標籤/搜索