上文講到在語義分析中會對Java中的語法糖進行解糖操做,所以本文就主要講述一下Java中有哪些語法糖,每一個語法糖在解糖事後的原始代碼,以及這些語法糖背後的邏輯。java
語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能並無影響,可是更方便程序員使用。一般來講使用語法糖可以增長程序的可讀性,從而減小程序代碼出錯的機會。 -----百度百科程序員
在編程領域中,除了語法糖的概念還有語法鹽,語法糖精,語法海洛因這些新奇的概念,感興趣的讀者自行Google一下,本文篇幅有限,就不展開來講了.web
從百度百科的描述來看,語法糖只是做爲一種更快捷的語法,其並不會改變所要實現的功能,所以本文就以Java中的語法糖來驗證一下是否如此.編程
源代碼以下所示數組
public class TestForEach { public static void main(String[] args) { // 驗證 循環迭代 ArrayList<Integer> test = new ArrayList<Integer>(); test.add(1); for (Integer integer : test) { System.out.println(integer); } Integer[] array = new Integer[test.size()]; array = test.toArray(array); for (Integer integer : array) { System.out.println(integer); } } }
用jad反編譯後以下所示jvm
public class TestForEach { public TestForEach() { } public static void main(String args[]) { ArrayList arraylist = new ArrayList(); arraylist.add(Integer.valueOf(1)); Integer integer; for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(integer)) integer = (Integer)iterator.next(); Integer ainteger[] = new Integer[arraylist.size()]; ainteger = (Integer[])arraylist.toArray(ainteger); Integer ainteger1[] = ainteger; int i = ainteger1.length; for(int j = 0; j < i; j++) { Integer integer1 = ainteger1[j]; System.out.println(integer1); } } }
根據上面反編譯後的代碼能夠看出集合元素的循環迭代底層是經過迭代器來實現的.而數組的循環則是經過原始的for循環來實現的.函數
經過上面的代碼咱們還能夠看出泛型這個概念在javac編譯時是不存在的,編譯器會將全部的泛型替換掉,在使用時,直接採用類型轉換的方式來獲得結果.也正是泛型的這個特徵可能出現下面這個問題.工具
public void test1(ArrayList<Integer> a){} public void test1(ArrayList<String> a){}
以上代碼是沒法編譯經過的,由於根據上文獲得的結論,泛型在編譯時,會消除全部的泛型的限定,那麼上面兩個方法的簽名都會一致,不知足函數重載的條件.優化
Java支持自動拆裝箱,即將基本類型和其包裝類型之間進行自動替換,那這種方式又是如何實現的呢.this
原始代碼以下所示:
// 自動拆裝箱 Integer a = 1; int b = new Integer(a);
通過反編譯後,代碼以下所示
Integer integer = Integer.valueOf(1); int i = (new Integer(integer.intValue())).intValue();
能夠看到Java實現基本類型 -- >包裝類型,是經過XXX.valueOf()
來實現的,而包裝類型 --> 基本類型是經過xxxValue()
來實現的.
咱們都知道switch--case只對int和char類型的數據有效,但從java7開始switch已經能夠支持String類型了,這背後的邏輯又是什麼,下面咱們反編譯一下代碼看看其本質是如何實現的.
String hello = "1"; switch (hello){ case "hello": System.out.println("Hello"); break; case "world": System.out.println("World"); break; default: System.out.println("HelloWorld"); break; }
初始代碼如上所示,咱們在switch中比較了字符串,根據字符串的不一樣來實現不一樣的分支,那這種邏輯是如何實現的呢.
String s = "1"; String s1 = s; byte byte0 = -1; switch(s1.hashCode()) { case 99162322: if(s1.equals("hello")) byte0 = 0; break; case 113318802: if(s1.equals("world")) byte0 = 1; break; } switch(byte0) { case 0: // '\0' System.out.println("Hello"); break; case 1: // '\001' System.out.println("World"); break; default: System.out.println("HelloWorld"); break; } }
能夠看到字符串是經過比較字符串的hashcode來進行比較,當兩個字符串的hashCode值相同時,再經過equals()來肯定其是否真正相同.
所以 Switch 比較 String 的本質仍是比較 int 類型的數據。
變長參數,即容許在方法調用時傳入不定數量的參數.具體使用以下所示:
public static void unSignedArgs(String... a){ for (String s : a) { System.out.println(s); } } public static void main(String[] args) { unSignedArgs("1","3","4"); }
在unSignedArgs()
方法中,咱們定義了一個變長參數,而後在方法調用的時候,傳入3個參數.那麼這種方法是如何實現的.
public static transient void unSignedArgs(String as[]) { String as1[] = as; int i = as1.length; for(int j = 0; j < i; j++) { String s = as1[j]; System.out.println(s); } } public static void main(String args[]) { unSignedArgs(new String[] { "1", "3", "4" }); } }
能夠看到變長參數,本質是經過數組來實現的,首先在方法定義時,將變長參數轉換爲了數組.而後在方法調用的時候是將傳入的參數轉換成數組而後再傳入定義的方法中。
在過去操做資源時,使用try-catch-finally
語句,須要開發人員手動在finally中關閉資源。但如今官方提倡使用 try-with-resource
來操做資源,那麼該語法是如何使用的呢。
源代碼以下所示:
try (FileInputStream fileInputStream = new FileInputStream("dfd")){ fileInputStream.read(); }catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
反編譯後代碼以下所示:
FileInputStream fileinputstream; Throwable throwable; Exception exception; fileinputstream = new FileInputStream("dfd"); throwable = null; try { fileinputstream.read(); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } finally { if(fileinputstream == null) goto _L0; else goto _L0 } if(fileinputstream != null) if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } else fileinputstream.close(); break MISSING_BLOCK_LABEL_104; if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else fileinputstream.close(); throw exception; Object obj; obj; ((FileNotFoundException) (obj)).printStackTrace(); break MISSING_BLOCK_LABEL_104; obj; ((IOException) (obj)).printStackTrace(); }
能夠看到編譯器自動幫助咱們進行資源的關閉,減小了編程人員出錯的可能.
數值字面量即在多位數值中穿插入_,方便開發人員快速掌握數值的大小.
int a = 10_000; System.out.println(a+1);
那麼這個語法糖的含義是什麼呢,反編譯後以下所示
char c = '\u2710'; System.out.println(c + 1);
從結果能夠看到編譯器是不會管下劃線的,其只會將數值正常的讀寫出來.
int a = 1; int b = 2; assert a == b; System.out.println(a+b);
翻譯後代碼以下所示
{ int i = 1; byte byte0 = 2; if(!$assertionsDisabled && i != byte0) { throw new AssertionError(); } else { System.out.println(i + byte0); return; } }
從代碼中能夠清楚地看到斷言的底層實現機制是用if
語句來實現,若是條件不符合,則拋出異常.
Java中的條件編譯,是經過永真或永假if
來實現的,編譯器會判斷條件是否符合,從而來判斷是否進行編譯.
源代碼以下所示:
// 條件編譯 if (true){ System.out.println("true"); }else{ System.out.println("false"); }
反編譯後代碼以下:
{ System.out.println("true"); }
從代碼中咱們能夠看到,編譯器對永遠不會執行的代碼進行了不編譯的處理,從而達到了條件編譯的效果.但其實筆者感受條件編譯在Java中用處不大,做用就是在不一樣的模式或機器下,能夠編譯執行不一樣的代碼.不過有總比沒有好.
在這裏咱們說內部類是一個語法糖,是由於其僅僅是一個編譯時的概念,在編譯階段,編譯器會將外部類和內部類進行編譯,從而生成兩個不一樣的文件,以下所示:
public class TestForEach { public class Children{ } } [260259@localhost src]$ ll 總用量 16 -rw-rw-r--. 1 260259 260259 331 5月 21 11:04 TestForEach$Children.class -rw-rw-r--. 1 260259 260259 335 5月 21 11:04 TestForEach.class -rw-rw-r--. 1 260259 260259 506 5月 21 11:04 TestForEach.jad -rw-rw-r--. 1 260259 260259 2206 5月 21 11:06 TestForEach.java
反編譯後,以下所示:
public class TestForEach { public class Children{ final TestForEach this$0; public Children(){ this$0 = TestForEach.this; super(); } }
枚舉是一種特殊的數據接口,其中包含了一種特殊的數據接口,以key-value 的形式來存儲數據,那麼 enum 是一種類嗎,其內部又是如何實現的呢。
首先咱們定義一個枚舉:
public enum testEnum { SPRING,SUMMER,AUTUMN,WINTER }
此時,咱們對這個枚舉進行反編譯,
public final class testEnum extends Enum { public static testEnum[] values() { return (testEnum[])$VALUES.clone(); } public static testEnum valueOf(String s) { return (testEnum)Enum.valueOf(testEnum, s); } private testEnum(String s, int i) { super(s, i); } public static final testEnum SPRING; public static final testEnum SUMMER; public static final testEnum AUTUMN; public static final testEnum WINTER; private static final testEnum $VALUES[]; static { SPRING = new testEnum("SPRING", 0); SUMMER = new testEnum("SUMMER", 1); AUTUMN = new testEnum("AUTUMN", 2); WINTER = new testEnum("WINTER", 3); $VALUES = (new testEnum[] { SPRING, SUMMER, AUTUMN, WINTER }); } }
從結果,咱們能夠看出,首先枚舉是一個編譯時的概念,這也說明了其是Java的一個語法糖,編譯器在編譯的時候自動生成了一個類繼承自Enum
,同時聲明爲final
,這也爲枚舉是不可繼承的提供了理論基礎.
lambda做爲Java8新出的一個功能點其實也是一種語法糖.由於筆者這邊沒有Java8及以上版本的反編譯工具,所以這邊就不詳細描述了,粗略說一下,lambda做爲一個語法糖,其內部實際上是經過相關的兩個底層Api來實現的.
在本文中,筆者介紹了Java中的12個語法糖,做爲開發人員瞭解這些語法糖的用法以及其內部的含義,可讓咱們更加高效地開發業務代碼,同時也可讓咱們瞭解編譯器的優化邏輯.從而提升程序的編寫效率和運行效率.
文章在公衆號"iceWang"第一手更新,有興趣的朋友能夠關注公衆號,第一時間看到筆者分享的各項知識點,謝謝!筆芯.
本系列文章主要借鑑自 <深刻分析javaweb技術內幕> 和 <深刻理解java虛擬機-jvm高級特性與最佳實踐> .