Java的語法糖

1.前言

  本文記錄內容來自《深刻理解Java虛擬機》的第十章早期(編譯期)優化其中一節內容,其餘的內容我的以爲暫時不須要過多關注,好比語法、詞法分析,語義分析和字節碼生成的過程等。主要關注的就是Java的一些語法糖是如何實現的。java

  語法糖不會提供實質性的功能改進,可是它們或能提升效率,或能提高語法的嚴謹性,或能減小編碼出錯的可能。大量使用語法糖可能會迷失其中,不得要領,下面就介紹一下Java的語法糖的實現。數組

2.泛型與類型擦除

  JDK5新增了一個特性,就是泛型。本質是參數化類型的應用,就是所操做的數據類型被指定爲一個參數,這種參數能夠應用在類、接口和方法的建立中。緩存

  Java沒有泛型的時候,只能經過Object是全部類型的父類和類型強制轉換兩個特色的配合來實現類型泛化,好比HashMap的get方法,返回的就是Object,由於map中一切皆有可能。可是這樣的操做帶來了一些風險,若是強轉的類型錯誤,就會在運行期間拋出異常,咱們須要在編碼期間就發現這個問題。性能

  Java的泛型是一種僞泛型,不是C#那種傳統意義上的泛型。在C#中,List<int>和List<String>是兩種類型,可是Java中仍是List。由於Java的泛型只存在於代碼中,編譯後泛型就消失了,這個就是所說的類型擦除,取而代之的就是插入了強轉代碼。將一段含有泛型的Java程序編譯後,再反編譯回來,就會發現反編譯的代碼中泛型消失了,新增的就是強轉代碼。優化

  爲何要使用類型擦除的方式實現泛型的緣由不得而知,可是這個確實是個吐槽的地方。有人說性能上泛型會因爲強制轉型操做和運行期缺乏針對類型的優化致使速度比真正的泛型慢。這個評價角度不太對,由於泛型是用於提高語義準確性的。Java的泛型會致使一些奇特的問題。編碼

  好比重載:public static void method(List<String> list)和public static void method(List<Integer> list)這兩個方法存在是不會編譯經過的,由於類型擦除後是特徵簽名如出一轍的。看似重載不正確的緣由找到了:重載要求方法名相同,參數不一樣,返回值可同可不一樣。這裏參數、方法名一致確定失敗了。但實際上不是,由於若是改一下一個返回String類型,一個返回Integer類型,按照重載的定義,返回值能夠不一樣,這兩個方法應該仍是同樣的,編譯不一樣過纔對,而其實是能夠經過的。這又是爲何呢?在Java語言中方法重載實際是要求方法具備不一樣的特徵簽名,相同的方法名。特徵簽名是一個方法中各個參數在常量池中的字段符號引用的集合,返回值不在其中,這樣也就能理解重載的要求是方法名相同,參數不一樣的含義了。而修改了返回值爲何經過了呢?緣由在於這不是重載的範疇,在Class文件格式中,只要描述符不是徹底一致的兩個方法也能夠共存。方法的描述符和返回值是有關係的,因此這兩個方法的描述符由於返回值的區別是不一樣的,能夠共存,但不是重載。對象

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

  從技術角度來講,這些語法糖在實現和思想上都不如泛型,可是這是使用最多的語法糖。blog

    自動裝箱的操做就是:Integer.valueof(i),在編譯階段替換成了這個接口

    自動拆箱的操做就是:Integer.intValue()資源

    集合的foreach操做是:for(its = list.iterator;  its.hasNext; ;) { its.next},採起的是迭代器的方式遍歷

    數組的foreach操做其實是壓棧,進棧出棧操做。

  這些原理都什麼簡單,能夠本身寫一個類編譯後,再反編譯看看結果。

  自動裝箱的陷阱:

    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        System.out.println(c == d);
        System.out.println(e == f);
        System.out.println(c == (a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g == (a+b));
        System.out.println(g.equals(a+b));
    }

  上面的代碼執行出來1.true 2.false 3.true 4.true 5.true 6.false。

  解釋一下上面的含義:

    首先包裝類的==比較不會觸發自動拆箱,因此一、2的比較是引用比較。那爲何1是true,2是false?上面說了自動裝箱是經過Integer.valueof實現的,Java代碼對Integer進行了優化,查看源代碼能夠看到有個緩存,在-128~127之間的整數,獲取的是同一個Integer對象,321超過了這個範圍,生成的是不一樣的對象。

    +號會觸發自動拆箱,致使==通常是int類型,而不是Integer類型,最後觸發c的自動拆箱,兩個int類型比較,值相等就是true了。

    4中的+號也會觸發自動拆箱,成int類型,但因爲是equals方法,又進行了自動裝箱,最後判斷標準就是Integer.equals的方法,其比較就是兩個int類型進行比較,因此是true

    5中和3的步驟相似,可是g自動才行成了long類型,long與int比較,數值的大小比較。

    6和4相似,可是最終變成了Long.equals(Integer),根據Long的equals方法,傳入的必須是Long類型纔可,因此返回的是false。

  這個例子告訴咱們代碼中避免這樣使用自動裝箱和拆箱,掌握不牢可能發生意料以外的狀況。

4.條件編譯

  許多語言都提供了條件編譯的途徑,好比C、C++的預處理器指示符#ifdef來完成條件編譯,他們是用於解決編譯時代碼的依賴關係,但在Java中沒有使用預處理器,由於不須要,編譯器不是一個個編譯Java文件,而是將全部編譯單元的語法樹頂級節點輸入到待處理列表後再進行編譯。

  Java語言也能夠實現條件編譯,方法就是使用條件是常量的if語句,好比if(true){} else{},這樣else模塊的內容就會被省略。

  這種條件編譯只能寫在方法體內,沒有辦法根據條件調整整個Java類結構。

5.其餘語法糖

  內部類、枚舉類、斷言語句、對枚舉和字符串(JDK7)的switch支持、try中定義和關閉資源等。這些能夠本身經過javac編譯後javap -verbose進行查看字節碼,理解其實現原理。

相關文章
相關標籤/搜索