摘要:操作對(duì)應(yīng)字節(jié)碼中的個(gè)字節(jié)我們可以看到最關(guān)鍵的操作其實(shí)就是調(diào)用的其實(shí)是類的方法,此方法的入?yún)㈩愋褪?,返回值類型是,翻譯過(guò)來(lái)就是類的方法,執(zhí)行完后將獲得的結(jié)果做了,檢查返回的對(duì)象類型是否是。
語(yǔ)法糖(Syntactic Sugar)的出現(xiàn)是為了降低我們編寫某些代碼時(shí)陷入的重復(fù)或繁瑣,這使得我們使用語(yǔ)法糖后可以寫出簡(jiǎn)明而優(yōu)雅的代碼。在Java中不加工的語(yǔ)法糖代碼運(yùn)行時(shí)可不會(huì)被虛擬機(jī)接受,因此編譯器為了讓這些含有語(yǔ)法糖的代碼正常工作其實(shí)需要對(duì)這些代碼進(jìn)行加工,經(jīng)過(guò)編譯器在生成class字節(jié)碼的階段完成解語(yǔ)法糖(desugar)的過(guò)程,那么這些語(yǔ)法糖最終究竟被編譯成了什么呢,在這里列舉了如下的一些Java典型的語(yǔ)法糖,結(jié)合實(shí)例和它們的編譯結(jié)果分析一下。本文為該系列的第一篇。
泛型和類型擦除java的泛型實(shí)際上是偽泛型,在編譯后編譯器會(huì)擦除泛型對(duì)象的參數(shù)化類型,也就是說(shuō)源代碼中的
另外,這個(gè)泛型信息不是真的就此丟掉了,class字節(jié)碼中還是會(huì)保留Signature屬性來(lái)記錄泛型對(duì)象在源碼中的參數(shù)化類型。
代碼:
public class Main { public static void main(String[] args) { ListstrList = new ArrayList<>(); strList.add("aaa"); String strEle = strList.get(0); } }
main方法在javap編譯后的字節(jié)碼
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."":()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
上面我們演示了一個(gè)參數(shù)化類型為String的List的泛型對(duì)象strList的add和get操作:
add操作:對(duì)應(yīng)字節(jié)碼中的8~16個(gè)字節(jié):我們可以看到最關(guān)鍵的add操作其實(shí)就是
invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
調(diào)用的其實(shí)是java/util/List類的add方法,此方法的入?yún)㈩愋褪?b>Ljava/lang/Object;,返回值類型是Z,翻譯過(guò)來(lái)就是List類的boolean add(Object o)方法,這里并沒(méi)有參數(shù)化類型String的什么事情。
get操作:對(duì)應(yīng)字節(jié)碼中的17~27個(gè)字節(jié):我們可以看到最關(guān)鍵的get操作其實(shí)就是
invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; checkcast #7 // class java/lang/String
調(diào)用的其實(shí)是java/util/List類的get方法,此方法的入?yún)㈩愋褪?b>I,返回值類型是Ljava/lang/Object;,翻譯過(guò)來(lái)就是List類的Object get(int i)方法,執(zhí)行完后將獲得的結(jié)果做了checkcast,檢查返回的對(duì)象類型是否是String。
從上面的分析我們不難看出,Java泛型到了編譯出結(jié)果的時(shí)候參數(shù)化類型已經(jīng)沒(méi)有什么作用了,就是簡(jiǎn)單做了強(qiáng)制的類型轉(zhuǎn)換。這段去掉了語(yǔ)法糖的代碼如下:
public class Main { public static void main(String[] args) { List strList = new ArrayList(); strList.add((Object)"aaa"); String strEle = (String) strList.get(0); } }
Java的泛型是偽泛型的原因如上,在運(yùn)行時(shí)這個(gè)代碼完全體會(huì)不到不同參數(shù)化類型的List有什么不同。而泛型參數(shù)化類型的用武之地更多的是在編譯時(shí)用來(lái)做檢驗(yàn)類型使用的,正常情況下如果編譯時(shí)通過(guò)檢驗(yàn)當(dāng)然就不會(huì)在運(yùn)行期類型強(qiáng)制轉(zhuǎn)換的時(shí)候出現(xiàn)異常,更何況其實(shí)字節(jié)碼中還有checkcast的顯式類型檢查。
如果使用javac的-g:vars參數(shù)來(lái)保留class字節(jié)碼中方法的局部變量信息,那么我們可以看到額外的信息:
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;
其中的LocalVariableTypeTable屬性記錄了strList的擦除泛型前的類型:Ljava/util/List
變長(zhǎng)參數(shù)會(huì)被編譯成為數(shù)組類型的參數(shù),變長(zhǎng)參數(shù)只能出現(xiàn)在參數(shù)列表的結(jié)尾以消除歧義。
代碼:
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,即參數(shù)是[Ljava/lang/String;,翻譯過(guò)來(lái)就是String[],即數(shù)組類型。
這段去掉了語(yǔ)法糖的代碼如下:
public class Main { public static void method(String[] args) { } }自動(dòng)裝箱拆箱
編譯后裝箱通過(guò)valueOf()變成了對(duì)象,拆箱通過(guò)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;編譯時(shí)x轉(zhuǎn)換成了java/lang/Integer.valueOf生成的引用類型Integer變量,而int y = x;編譯時(shí)y轉(zhuǎn)換成了java/lang/Integer.intValue生成的原始類型int變量。
去掉了語(yǔ)法糖的代碼如下:
public class Main { public static void main(String[] args) { Integer x = Integer.valueOf(1); int y = x.intValue(); } }遍歷循環(huán)
編譯后變成了迭代器遍歷。
代碼:
public class Main { public static void main(String[] args) { ListstrList = 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."":()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
從上面我們可以看到遍歷循環(huán)的語(yǔ)法糖被替換成了List.iterator的循環(huán)操作,用下面的代碼即可表達(dá)這段編譯后的去掉語(yǔ)法糖的代碼:
public class Main { public static void main(String[] args) { List條件編譯strList = new ArrayList<>(); Iterator strIterator = strList.iterator(); while(strIterator.hasNext()){ System.out.println((String) strIterator.next()); } } }
編譯后將常量不可達(dá)條件分支直接在編譯結(jié)果中消除掉。
代碼:
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; }
從上面我們可以看到常量不可達(dá)條件直接就在編譯結(jié)果中略去了,仿佛就沒(méi)有這個(gè)分支一樣,用下面的代碼即可表達(dá)這段編譯后的去掉語(yǔ)法糖的代碼:
public class Main { public static void main(String[] args) { System.out.println("Yes"); } }
需要注意的是這里強(qiáng)調(diào)的是常量不可達(dá)條件才會(huì)略去,比如直接就是true的分支或者1==1這樣的分支是會(huì)保留的,如果是變量經(jīng)過(guò)運(yùn)算后才被確定為不可達(dá)是不會(huì)發(fā)生這種條件編譯的,比如:
public class Main { public static void main(String[] args) { int i = 1; if (i==1) { System.out.println("Yes"); } else { System.out.println("No"); } } }
編譯后還是會(huì)走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 */ }內(nèi)部類
內(nèi)部類即是類中類,我們來(lái)看這個(gè)簡(jiǎn)單的例子:
代碼:
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); } }
來(lái)看看編譯后的結(jié)果,編譯后會(huì)將內(nèi)部類Person多帶帶拿出來(lái)做編譯,不過(guò)語(yǔ)法糖褪去后編譯器做了一些處理,比如為Person類加了與外部的Main類相聯(lián)系的字段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."":()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; }
這里翻譯過(guò)來(lái)類似這樣的:
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); } }
至于為什么需要這個(gè)多余的外部類的字段呢,其實(shí)是為了通過(guò)它來(lái)獲取外部類中的信息,我們對(duì)例子加以改造,添加兩個(gè)外部類的字段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(); } }
這個(gè)時(shí)候編譯的結(jié)果是Main為了對(duì)外提供自己屬性的值自動(dòng)添加了靜態(tài)方法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; }
而內(nèi)部類編譯后的結(jié)果在獲取外部類的屬性的時(shí)候其實(shí)就是調(diào)用暴露出的這些方法:
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; }
翻譯過(guò)來(lái)其實(shí)就是這樣子的:
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; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/77050.html
摘要:因此,對(duì)應(yīng)地我們可以翻譯這段二進(jìn)制字節(jié)碼為這樣的代碼注意,這段代碼并不能通過(guò)編譯,因?yàn)樵创a這一層是不允許直接繼承的,這個(gè)繼承過(guò)程只允許在編譯器內(nèi)部解語(yǔ)法糖的過(guò)程中被編譯器添加,添加之后的類才會(huì)有的訪問(wèn)標(biāo)識(shí)符。 語(yǔ)法糖(Syntactic Sugar)的出現(xiàn)是為了降低我們編寫某些代碼時(shí)陷入的重復(fù)或繁瑣,這使得我們使用語(yǔ)法糖后可以寫出簡(jiǎn)明而優(yōu)雅的代碼。在Java中不加工的語(yǔ)法糖代碼運(yùn)行時(shí)可...
摘要:但其實(shí),虛擬機(jī)并不支持這些語(yǔ)法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來(lái)了解一下嗎); 本文從 ...
摘要:但其實(shí),虛擬機(jī)并不支持這些語(yǔ)法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來(lái)了解一下嗎); 本文從 ...
摘要:但其實(shí),虛擬機(jī)并不支持這些語(yǔ)法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來(lái)了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來(lái)了解一下嗎); 本文從 ...
摘要:提供給了用戶大量的語(yǔ)法糖,比如泛型自動(dòng)裝箱拆箱循環(huán)變長(zhǎng)參數(shù)內(nèi)部類枚舉類斷言新特性方法引用等解語(yǔ)法糖語(yǔ)法糖的存在主要是方便開(kāi)發(fā)人員使用。 首先,部分總結(jié)文字引用 簡(jiǎn)書作者:Eric新之助 。鏈接:https://www.jianshu.com/p/4de08deb6ba4 已獲得授權(quán) showImg(https://segmentfault.com/img/bVbfuX9?w=646&...
閱讀 703·2021-10-13 09:39
閱讀 1526·2021-09-09 11:53
閱讀 2728·2019-08-29 13:55
閱讀 772·2019-08-28 18:08
閱讀 2665·2019-08-26 13:54
閱讀 2476·2019-08-26 11:44
閱讀 1893·2019-08-26 11:41
閱讀 3928·2019-08-26 10:15