摘要:因此,對(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í)可不會(huì)被虛擬機(jī)接受,因此編譯器為了讓這些含有語(yǔ)法糖的代碼正常工作其實(shí)需要對(duì)這些代碼進(jìn)行加工,經(jīng)過(guò)編譯器在生成class字節(jié)碼的階段完成解語(yǔ)法糖(desugar)的過(guò)程,那么這些語(yǔ)法糖最終究竟被編譯成了什么呢,在這里列舉了如下的一些Java典型的語(yǔ)法糖,結(jié)合實(shí)例和它們的編譯結(jié)果分析一下。本文為本系列第二篇。
枚舉類枚舉在編譯后會(huì)變成一個(gè)特殊的final類,因此枚舉類型是名副其實(shí)的不可變類,我們通過(guò)下面最簡(jiǎn)單的例子來(lái)仔細(xì)分析一下:
源碼:
enum COLOR { RED, BLUE, GREEN }
使用這個(gè)枚舉的時(shí)候我們可以發(fā)現(xiàn)有valueOf(String)和values()這樣的方法可以用,因此不難猜測(cè)編譯器會(huì)添加一些未在源碼中出現(xiàn)的其他增強(qiáng)二進(jìn)制字節(jié)碼,可以看一下具體的字節(jié)碼:
final class COLOR extends java.lang.Enumminor version: 0 major version: 52 flags: ACC_FINAL, ACC_SUPER, ACC_ENUM ... { public static final COLOR RED; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR BLUE; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR GREEN; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static COLOR[] values(); descriptor: ()[LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LCOLOR; 3: invokevirtual #2 // Method "[LCOLOR;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LCOLOR;" 9: areturn public static COLOR valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class COLOR 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class COLOR 9: areturn static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #4 // class COLOR 3: dup 4: ldc #7 // String RED 6: iconst_0 7: invokespecial #8 // Method " ":(Ljava/lang/String;I)V 10: putstatic #9 // Field RED:LCOLOR; 13: new #4 // class COLOR 16: dup 17: ldc #10 // String BLUE 19: iconst_1 20: invokespecial #8 // Method " ":(Ljava/lang/String;I)V 23: putstatic #11 // Field BLUE:LCOLOR; 26: new #4 // class COLOR 29: dup 30: ldc #12 // String GREEN 32: iconst_2 33: invokespecial #8 // Method " ":(Ljava/lang/String;I)V 36: putstatic #13 // Field GREEN:LCOLOR; 39: iconst_3 40: anewarray #4 // class COLOR 43: dup 44: iconst_0 45: getstatic #9 // Field RED:LCOLOR; 48: aastore 49: dup 50: iconst_1 51: getstatic #11 // Field BLUE:LCOLOR; 54: aastore 55: dup 56: iconst_2 57: getstatic #13 // Field GREEN:LCOLOR; 60: aastore 61: putstatic #1 // Field $VALUES:[LCOLOR; 64: return } Signature: #32 // Ljava/lang/Enum ;
這段字節(jié)碼可以證實(shí)出上面的猜測(cè),確實(shí)會(huì)有額外的二進(jìn)制字節(jié)碼被添加了,枚舉類會(huì)被編譯成為Ljava/lang/Enum的子類COLOR,而枚舉類型中的枚舉項(xiàng)會(huì)被編譯成為COLOR類的常量字段,而且COLOR內(nèi)部還會(huì)維護(hù)一個(gè)數(shù)組來(lái)保存這些常量字段,并進(jìn)而添加valueOf(String)和values()來(lái)訪問(wèn)這個(gè)數(shù)組。因此,對(duì)應(yīng)地我們可以翻譯這段二進(jìn)制字節(jié)碼為這樣的代碼:
final class COLOR extends Enum{ private static final COLOR RED; private static final COLOR BLUE; private static final COLOR GREEN; private static final COLOR[] $VALUES; static { RED = new COLOR("RED", 0); BLUE = new COLOR("BLUE", 1); GREEN = new COLOR("GREEN", 2); COLOR[] $COLOR_ARRAY = new COLOR[3]; $COLOR_ARRAY[0] = RED; $COLOR_ARRAY[1] = BLUE; $COLOR_ARRAY[2] = GREEN; $VALUES = $COLOR_ARRAY; } private COLOR(String color, int ordinal) { super(color, ordinal); } public static COLOR[] values() { return $VALUES.clone(); } public static COLOR valueOf(String color) { return Enum.valueOf(COLOR.class, color); } }
注意,這段代碼并不能通過(guò)編譯,因?yàn)樵创a這一層是不允許直接繼承Ljava/lang/Enum的,這個(gè)繼承過(guò)程只允許在編譯器內(nèi)部解語(yǔ)法糖的過(guò)程中被編譯器添加,添加之后的類才會(huì)有ACC_ENUM的訪問(wèn)標(biāo)識(shí)符。
我們可以看到的是在Ljava/lang/Enum內(nèi)部實(shí)際上有name和ordinal常量來(lái)標(biāo)識(shí)一個(gè)枚舉項(xiàng),name會(huì)由枚舉項(xiàng)名來(lái)設(shè)置,而ordinal是枚舉項(xiàng)序號(hào),由枚舉項(xiàng)排列順序決定。
我們?cè)賮?lái)看一下帶有字段的枚舉項(xiàng)編譯后的效果。
源碼:
enum COLOR { RED(0), BLUE(1), GREEN(2); int code; COLOR(int code) { this.code = code; } }
編譯后的字節(jié)碼:
final class COLOR extends java.lang.Enumminor version: 0 major version: 52 flags: ACC_FINAL, ACC_SUPER, ACC_ENUM ... { public static final COLOR RED; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR BLUE; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR GREEN; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM int code; descriptor: I flags: public static COLOR[] values(); descriptor: ()[LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LCOLOR; 3: invokevirtual #2 // Method "[LCOLOR;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LCOLOR;" 9: areturn LineNumberTable: line 1: 0 public static COLOR valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class COLOR 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class COLOR 9: areturn LineNumberTable: line 1: 0 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=5, locals=0, args_size=0 0: new #4 // class COLOR 3: dup 4: ldc #8 // String RED 6: iconst_0 7: iconst_0 8: invokespecial #9 // Method " ":(Ljava/lang/String;II)V 11: putstatic #10 // Field RED:LCOLOR; 14: new #4 // class COLOR 17: dup 18: ldc #11 // String BLUE 20: iconst_1 21: iconst_1 22: invokespecial #9 // Method " ":(Ljava/lang/String;II)V 25: putstatic #12 // Field BLUE:LCOLOR; 28: new #4 // class COLOR 31: dup 32: ldc #13 // String GREEN 34: iconst_2 35: iconst_2 36: invokespecial #9 // Method " ":(Ljava/lang/String;II)V 39: putstatic #14 // Field GREEN:LCOLOR; 42: iconst_3 43: anewarray #4 // class COLOR 46: dup 47: iconst_0 48: getstatic #10 // Field RED:LCOLOR; 51: aastore 52: dup 53: iconst_1 54: getstatic #12 // Field BLUE:LCOLOR; 57: aastore 58: dup 59: iconst_2 60: getstatic #14 // Field GREEN:LCOLOR; 63: aastore 64: putstatic #1 // Field $VALUES:[LCOLOR; 67: return LineNumberTable: line 2: 0 line 3: 14 line 4: 28 line 1: 42 } Signature: #36 // Ljava/lang/Enum ;
用java源碼翻譯下上面的結(jié)果:
final class COLOR extends Enum{ private static final COLOR RED; private static final COLOR BLUE; private static final COLOR GREEN; int code; private static final COLOR[] $VALUES; static { RED = new COLOR("RED", 0, 0); BLUE = new COLOR("BLUE", 1, 1); GREEN = new COLOR("GREEN", 2, 2); COLOR[] $COLOR_ARRAY = new COLOR[3]; $COLOR_ARRAY[0] = RED; $COLOR_ARRAY[1] = BLUE; $COLOR_ARRAY[2] = GREEN; $VALUES = $COLOR_ARRAY; } private COLOR(String color, int ordinal, int code) { super(color, ordinal); this.code = code; } public static COLOR[] values() { return $VALUES.clone(); } public static COLOR valueOf(String color) { return Enum.valueOf(COLOR.class, color); } }
其實(shí)有了之前的基礎(chǔ)很容易看出來(lái),新增加的code字段最終只是變成了編譯器生成的COLOR類的一個(gè)字段,唯一的變化就是編譯出的初始化方法也會(huì)增加為這個(gè)字段而添加的參數(shù)。
斷言java 1.4引入的斷言,使用關(guān)鍵字assert來(lái)判斷一個(gè)條件是否為true,通過(guò)如下的源碼來(lái)分析一下:
class Main { public static void main(String[] args) { String judge = "yes"; assert "no".equals(judge); } }
斷言在運(yùn)行時(shí)默認(rèn)是關(guān)閉的,我們可以通過(guò)運(yùn)行時(shí)打開斷言來(lái)啟用:java -ea Main:
at Main.main(Main.java:4)
那么我們來(lái)看一下編譯后的字節(jié)碼:
{ static final boolean $assertionsDisabled; descriptor: Z flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC Main(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 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: ldc #2 // String yes 2: astore_1 3: getstatic #3 // Field $assertionsDisabled:Z 6: ifne 26 9: ldc #4 // String no 11: aload_1 12: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 15: ifne 26 18: new #6 // class java/lang/AssertionError 21: dup 22: invokespecial #7 // Method java/lang/AssertionError." ":()V 25: athrow 26: return StackMapTable: number_of_entries = 1 frame_type = 252 /* append */ offset_delta = 26 locals = [ class java/lang/String ] static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #8 // class Main 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z 5: ifne 12 8: iconst_1 9: goto 13 12: iconst_0 13: putstatic #3 // Field $assertionsDisabled:Z 16: return StackMapTable: number_of_entries = 2 frame_type = 12 /* same */ frame_type = 64 /* same_locals_1_stack_item */ stack = [ int ] }
可以發(fā)現(xiàn)編譯器為Main類添加了字段$assertionsDisabled,此字段即是啟用斷言的關(guān)鍵。在運(yùn)行時(shí)加入啟用斷言的-ea會(huì)使得類初始化時(shí)Class.desiredAssertionStatus為真,進(jìn)而字段$assertionsDisabled為真,這個(gè)邏輯在上述的字節(jié)碼中可以看出。在斷言的地方,如果條件為真則會(huì)正常返回,如果條件為false則會(huì)拋出java/lang/AssertionError錯(cuò)誤導(dǎo)致程序終止。
用java源碼翻譯下上面的結(jié)果:
class Main { private static final boolean $assertionsDisabled; static { if (Main.class.desiredAssertionStatus()) { $assertionsDisabled = true; } else { $assertionsDisabled = false; } } public static void main(String[] args) { if($assertionsDisabled) { if (!"no".equals("yes")) { throw new AssertionError(); } } } }switch處理枚舉和字符串
我們先來(lái)看看在java 1.7以前就可以使用switch的類型在字節(jié)碼層是如何工作的,這里以int類型為例:
class Main { public static void main(String[] args) { int a = 1; switch (a) { case 0: System.out.println("0"); break; case 2: System.out.println("1"); break; case 8: System.out.println("3"); break; default: break; } } }
編譯后的字節(jié)碼:
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: lookupswitch { // 3 0: 36 2: 47 8: 58 default: 69 } 36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 39: ldc #3 // String 0 41: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 44: goto 69 47: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 50: ldc #5 // String 1 52: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 55: goto 69 58: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 61: ldc #6 // String 3 63: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: goto 69 69: return }
這里是用的字節(jié)碼命令lookupswitch適用于判斷switch的跳轉(zhuǎn)語(yǔ)句的,即如果switch 0跳轉(zhuǎn)到26行、switch 2跳轉(zhuǎn)到47行、switch 8跳轉(zhuǎn)到58行、其他跳轉(zhuǎn)到69行。
受限于lookupswitch判斷的條件的類型,在java 1.7以前是無(wú)法對(duì)非32位數(shù)字類型的類型做判斷的,而java 1.7以后通過(guò)語(yǔ)法糖的解析實(shí)現(xiàn)了字符串的switch分支判斷,可以想到的是,在不改變lookupswitch的能力的情況下,編譯器會(huì)將字符串轉(zhuǎn)換為32位數(shù)字。我們寫這樣的例子來(lái)分析下:
class Main { public static void main(String[] args) { String a = args[0]; switch (a) { case "a": System.out.println("a"); break; case "b": System.out.println("b"); break; default: break; } } }
編譯后的結(jié)果:
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: aload_0 1: iconst_0 2: aaload 3: astore_1 4: aload_1 5: astore_2 6: iconst_m1 7: istore_3 8: aload_2 9: invokevirtual #2 // Method java/lang/String.hashCode:()I 12: lookupswitch { // 2 97: 40 98: 54 default: 65 } 40: aload_2 41: ldc #3 // String a 43: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 46: ifeq 65 49: iconst_0 50: istore_3 51: goto 65 54: aload_2 55: ldc #5 // String b 57: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 60: ifeq 65 63: iconst_1 64: istore_3 65: iload_3 66: lookupswitch { // 2 0: 92 1: 103 default: 114 } 92: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 95: ldc #3 // String a 97: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 100: goto 114 103: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 106: ldc #5 // String b 108: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 111: goto 114 114: return LocalVariableTable: Start Length Slot Name Signature 0 115 0 args [Ljava/lang/String; 4 111 1 a Ljava/lang/String; }
我們可以發(fā)現(xiàn)編譯器對(duì)要做分支判斷的字符串計(jì)算了它的hashcode,而這個(gè)hashcode是符合lookupswitch要求的32位數(shù)字,因此將這個(gè)hashcode做lookupswitch分支判斷,和switch條件中的"a"、"b"的hashcode做比較,如果進(jìn)入了其中某個(gè)分支如"a"分支,則在分支中判斷"a"和字符串是否相等,如果相等則確定此分支是正確的(只有hashcode相等并不能確定是值相等,hashcode的沖突原理不再展開),接下來(lái)再將分支條件直接設(shè)置為0、1、2這樣的簡(jiǎn)單條件執(zhí)行下一輪lookupswitch。我們同樣可以用如下java源碼翻譯下上面的結(jié)果:
class Main { public static void main(String[] args) { String param = args[0]; int hashcode = param.hashCode(); final int condition_a = 97; //"a".hashCode() final int condition_b = 98; //"b".hashCode(); int hashcodeSwitchResult = -1; switch (hashcode) { case condition_a: if("a".equals(param)){ hashcodeSwitchResult = 0; } break; case condition_b: if("b".equals(param)){ hashcodeSwitchResult = 1; } break; default: break; } switch (hashcodeSwitchResult) { case 0: System.out.println("a"); break; case 1: System.out.println("b"); break; default: break; } } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/77134.html
摘要:操作對(duì)應(yīng)字節(jié)碼中的個(gè)字節(jié)我們可以看到最關(guān)鍵的操作其實(shí)就是調(diào)用的其實(shí)是類的方法,此方法的入?yún)㈩愋褪牵祷刂殿愋褪牵g過(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ī)接受,因此...
摘要:但其實(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ǔ)法糖的存在主要是方便開發(fā)人員使用。 首先,部分總結(jié)文字引用 簡(jiǎn)書作者:Eric新之助 。鏈接:https://www.jianshu.com/p/4de08deb6ba4 已獲得授權(quán) showImg(https://segmentfault.com/img/bVbfuX9?w=646&...
閱讀 4418·2021-11-19 09:59
閱讀 3335·2021-10-12 10:12
閱讀 2646·2021-09-22 15:25
閱讀 3349·2019-08-30 15:55
閱讀 1194·2019-08-29 11:27
閱讀 1473·2019-08-28 18:06
閱讀 2747·2019-08-26 13:41
閱讀 2564·2019-08-26 13:41