語法糖(Syntactic Sugar)的出現是爲了下降咱們編寫某些代碼時陷入的重複或繁瑣,這使得咱們使用語法糖後能夠寫出簡明而優雅的代碼。在Java中不加工的語法糖代碼運行時可不會被虛擬機接受,所以編譯器爲了讓這些含有語法糖的代碼正常工做其實須要對這些代碼進行加工,通過編譯器在生成class字節碼的階段完成解語法糖(desugar)的過程,那麼這些語法糖最終究竟被編譯成了什麼呢,在這裏列舉了以下的一些Java典型的語法糖,結合實例和它們的編譯結果分析一下。本文爲該系列的第一篇。java
java的泛型其實是僞泛型,在編譯後編譯器會擦除泛型對象的參數化類型,也就是說源代碼中的<T>類型
其實都會擦除,最終成爲class字節碼中的Object類型
,賦值等操做也就會直接轉換爲強制的類型轉換,這樣作無風險的緣由是在編譯的標註檢查階段其實已經進行了泛型的檢查,若是當時沒法經過檢查的話編譯沒法經過。數組
另外,這個泛型信息不是真的就此丟掉了,class字節碼中仍是會保留Signature
屬性來記錄泛型對象在源碼中的參數化類型。app
代碼:this
public class Main { public static void main(String[] args) { List<String> strList = new ArrayList<>(); strList.add("aaa"); String strEle = strList.get(0); } }
main方法在javap編譯後的字節碼翻譯
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String aaa 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: aload_1 18: iconst_0 19: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 24: checkcast #7 // class java/lang/String 27: astore_2 28: return
上面咱們演示了一個參數化類型爲String
的List
的泛型對象strList
的add
和get
操做:code
add
操做:對應字節碼中的8~16個字節:咱們能夠看到最關鍵的add操做其實就是對象
invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
調用的實際上是java/util/List
類的add
方法,此方法的入參類型是Ljava/lang/Object;
,返回值類型是Z
,翻譯過來就是List類的boolean add(Object o)
方法,這裏並無參數化類型String
的什麼事情。ip
get
操做:對應字節碼中的17~27個字節:咱們能夠看到最關鍵的get操做其實就是ci
invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; checkcast #7 // class java/lang/String
調用的實際上是java/util/List
類的get
方法,此方法的入參類型是I
,返回值類型是Ljava/lang/Object;
,翻譯過來就是List類的Object get(int i)
方法,執行完後將得到的結果作了checkcast
,檢查返回的對象類型是不是String
。get
從上面的分析咱們不難看出,Java泛型到了編譯出結果的時候參數化類型已經沒有什麼做用了,就是簡單作了強制的類型轉換。這段去掉了語法糖的代碼以下:
public class Main { public static void main(String[] args) { List strList = new ArrayList(); strList.add((Object)"aaa"); String strEle = (String) strList.get(0); } }
Java的泛型是僞泛型的緣由如上,在運行時這個代碼徹底體會不到不一樣參數化類型的List有什麼不一樣。而泛型參數化類型的用武之地更多的是在編譯時用來作檢驗類型使用的,正常狀況下若是編譯時經過檢驗固然就不會在運行期類型強制轉換的時候出現異常,更況且其實字節碼中還有checkcast
的顯式類型檢查。
若是使用javac
的-g:vars
參數來保留class字節碼中方法的局部變量信息,那麼咱們能夠看到額外的信息:
LocalVariableTable: Start Length Slot Name Signature 0 29 0 args [Ljava/lang/String; 8 21 1 strList Ljava/util/List; 28 1 2 strEle Ljava/lang/String; LocalVariableTypeTable: Start Length Slot Name Signature 8 21 1 strList Ljava/util/List<Ljava/lang/String;>;
其中的LocalVariableTypeTable
屬性記錄了strList
的擦除泛型前的類型:Ljava/util/List<Ljava/lang/String;>;
,翻譯過來其實就是List<String>
,若是在反射中獲取泛型變量的類型元信息,其來源其實就是這個Signature。這也算是Java爲了彌補因類型擦除而致使的class字節碼中的類型數據缺失而作出的額外努力吧。
變長參數會被編譯成爲數組類型的參數,變長參數只能出如今參數列表的結尾以消除歧義。
代碼:
public class Main { public static void method(String... args) { } }
method方法在編譯後:
public static void method(java.lang.String...); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=0, locals=1, args_size=1 0: return
咱們能夠清楚地看到方法的特徵符是([Ljava/lang/String;)V
,即參數是[Ljava/lang/String;
,翻譯過來就是String[]
,即數組類型。
這段去掉了語法糖的代碼以下:
public class Main { public static void method(String[] args) { } }
編譯後裝箱經過valueOf()變成了對象,拆箱經過xxxValue()變成了原始類型值。
代碼:
public class Main { public static void main(String[] args) { Integer x = 1; int y = x; } }
main方法編譯後:
descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: aload_1 6: invokevirtual #3 // Method java/lang/Integer.intValue:()I 9: istore_2 10: return
這裏咱們能夠明顯看到Integer x = 1;
編譯時x
轉換成了java/lang/Integer.valueOf
生成的引用類型Integer
變量,而int y = x;
編譯時y
轉換成了java/lang/Integer.intValue
生成的原始類型int
變量。
去掉了語法糖的代碼以下:
public class Main { public static void main(String[] args) { Integer x = Integer.valueOf(1); int y = x.intValue(); } }
編譯後變成了迭代器遍歷。
代碼:
public class Main { public static void main(String[] args) { List<String> strList = new ArrayList<>(); for (String str : strList) { System.out.println(str); } } }
main方法編譯後:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: invokeinterface #4, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 14: astore_2 15: aload_2 16: invokeinterface #5, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 21: ifeq 44 24: aload_2 25: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 30: checkcast #7 // class java/lang/String 33: astore_3 34: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 37: aload_3 38: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: goto 15 44: return StackMapTable: number_of_entries = 2 frame_type = 253 /* append */ offset_delta = 15 locals = [ class java/util/List, class java/util/Iterator ] frame_type = 250 /* chop */ offset_delta = 28
從上面咱們能夠看到遍歷循環的語法糖被替換成了List.iterator
的循環操做,用下面的代碼便可表達這段編譯後的去掉語法糖的代碼:
public class Main { public static void main(String[] args) { List<String> strList = new ArrayList<>(); Iterator strIterator = strList.iterator(); while(strIterator.hasNext()){ System.out.println((String) strIterator.next()); } } }
編譯後將常量不可達條件分支直接在編譯結果中消除掉。
代碼:
public class Main { public static void main(String[] args) { if (true) { System.out.println("Yes"); } else { System.out.println("No"); } } }
main方法編譯後:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Yes 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; }
從上面咱們能夠看到常量不可達條件直接就在編譯結果中略去了,彷彿就沒有這個分支同樣,用下面的代碼便可表達這段編譯後的去掉語法糖的代碼:
public class Main { public static void main(String[] args) { System.out.println("Yes"); } }
須要注意的是這裏強調的是常量不可達條件纔會略去,好比直接就是true的分支或者1==1
這樣的分支是會保留的,若是是變量通過運算後才被肯定爲不可達是不會發生這種條件編譯的,好比:
public class Main { public static void main(String[] args) { int i = 1; if (i==1) { System.out.println("Yes"); } else { System.out.println("No"); } } }
編譯後仍是會走ifelse
判斷:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: iconst_1 1: istore_1 2: iload_1 3: iconst_1 4: if_icmpne 18 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #3 // String Yes 12: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: goto 26 18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 21: ldc #5 // String No 23: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 26: return LocalVariableTable: Start Length Slot Name Signature 0 27 0 args [Ljava/lang/String; 2 25 1 i I StackMapTable: number_of_entries = 2 frame_type = 252 /* append */ offset_delta = 18 locals = [ int ] frame_type = 7 /* same */ }
內部類便是類中類,咱們來看這個簡單的例子:
代碼:
public class Main { class Person{ String name; Integer age; public Person(String name, Integer age) { this.name = name; this.age = age; } } public void demo(String[] args) { Person person = new Person("ccc", 20); } }
來看看編譯後的結果,編譯後會將內部類Person
單獨拿出來作編譯,不過語法糖褪去後編譯器作了一些處理,好比爲Person
類加了與外部的Main
類相聯繫的字段this$0
:
... class top.jinhaoplus.Main$Person ... { java.lang.String name; descriptor: Ljava/lang/String; flags: java.lang.Integer age; descriptor: Ljava/lang/Integer; flags: final top.jinhaoplus.Main this$0; descriptor: Ltop/jinhaoplus/Main; flags: ACC_FINAL, ACC_SYNTHETIC public top.jinhaoplus.Main$Person(top.jinhaoplus.Main, java.lang.String, java.lang.Integer); descriptor: (Ltop/jinhaoplus/Main;Ljava/lang/String;Ljava/lang/Integer;)V flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=4 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:Ltop/jinhaoplus/Main; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: aload_0 10: aload_2 11: putfield #3 // Field name:Ljava/lang/String; 14: aload_0 15: aload_3 16: putfield #4 // Field age:Ljava/lang/Integer; 19: return LocalVariableTable: Start Length Slot Name Signature 0 20 0 this Ltop/jinhaoplus/Main$Person; 0 20 1 this$0 Ltop/jinhaoplus/Main; 0 20 2 name Ljava/lang/String; 0 20 3 age Ljava/lang/Integer; }
這裏翻譯過來相似這樣的:
class Person { String name; Integer age; final Main this$0; public Person(final Main this$0, String name, Integer age) { this.this$0 = this$0; this.name = name; this.age = age; } } public class Main { public void demo(String[] args) { Person person = new Person(this, "ccc", 20); } }
至於爲何須要這個多餘的外部類的字段呢,實際上是爲了經過它來獲取外部類中的信息,咱們對例子加以改造,添加兩個外部類的字段secret1
和secret2
:
public class Main { private String secret1; private String secret2; class Person{ String name; Integer age; public Person(String name, Integer age) { this.name = name; this.age = age; } public void getSecrets(){ System.out.println(secret1); System.out.println(secret2); } } public void demo(String[] args) { Person person = new Person("ccc", 20); person.getSecrets(); } }
這個時候編譯的結果是Main
爲了對外提供本身屬性的值自動添加了靜態方法access$000(Main)
和access$100(Main)
:
static java.lang.String access$000(top.jinhaoplus.Main); descriptor: (Ltop/jinhaoplus/Main;)Ljava/lang/String; flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field secret1:Ljava/lang/String; 4: areturn LocalVariableTable: Start Length Slot Name Signature 0 5 0 x0 Ltop/jinhaoplus/Main; static java.lang.String access$100(top.jinhaoplus.Main); descriptor: (Ltop/jinhaoplus/Main;)Ljava/lang/String; flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field secret2:Ljava/lang/String; 4: areturn LocalVariableTable: Start Length Slot Name Signature 0 5 0 x0 Ltop/jinhaoplus/Main; }
而內部類編譯後的結果在獲取外部類的屬性的時候其實就是調用暴露出的這些方法:
public void getSecret(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: getfield #1 // Field this$0:Ltop/jinhaoplus/Main; 7: invokestatic #6 // Method top/jinhaoplus/Main.access$000:(Ltop/jinhaoplus/Main;)Ljava/lang/String; 10: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 16: aload_0 17: getfield #1 // Field this$0:Ltop/jinhaoplus/Main; 20: invokestatic #8 // Method top/jinhaoplus/Main.access$100:(Ltop/jinhaoplus/Main;)Ljava/lang/String; 23: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 26: return LocalVariableTable: Start Length Slot Name Signature 0 27 0 this Ltop/jinhaoplus/Main$Person; }
翻譯過來其實就是這樣子的:
class Person { String name; Integer age; final Main this$0; public Person(final Main this$0, String name, Integer age) { this.this$0 = this$0; this.name = name; this.age = age; } public void getSecrets(){ System.out.println(Main.access$000(this$0)); System.out.println(Main.access$100(this$0)); } } public class Main { private String secret1; private String secret2; public void demo(String[] args) { Person person = new Person(this, "ccc", 20); } public static String access$000(Main main) { return main.secret1; } public static String access$100(Main main) { return main.secret2; } }