摘要:一道String字符串比較問題引發(fā)的字節(jié)碼分析public class a { public static void main(String[] args)throws Exception{ &nbs
一道String字符串比較問題引發(fā)的字節(jié)碼分析
public class a { public static void main(String[] args)throws Exception{ } public static void aa(){ String s1="a";//內(nèi)存在方法區(qū)的常量池 String s2="b";//內(nèi)存在方法區(qū)的常量池 String s12 = "ab";//內(nèi)存在方法區(qū)的常量池 String s3 = s1 + s2;//s3的內(nèi)存所在??? p(s3==s12);//false } public static void bb(){ String s1="a"+"b";//s1的內(nèi)存所在??? String s2 = "ab";//內(nèi)存在方法區(qū)的常量池 p(s1==s2);//true }public static void p(Object obj){ System.out.println(obj); } }
這是我們經(jīng)常碰到的煩人的String比較問題,要得到答案,就要弄清楚aa()方法中的s3的內(nèi)存在哪里?,和bb()方法中的s1的內(nèi)存在哪里?
不多說,貼上a.class文件反編譯的字節(jié)碼指令:
首先是 aa()方法:
public static void aa(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=0 共4個本地變量空間 0: ldc #3 // String a 將字符串"a"從常量池中推送至棧頂 2: astore_0 將棧頂引用類型(即字符串"a")存入第一個本地變量 3: ldc #4 // String b 將字符串"b"從常量池推送至棧頂 5: astore_1 將棧頂引用類型(即字符串"b")存入第二個本地變量 6: ldc #5 // String ab 將字符串"ab"從常量池推送至棧頂 8: astore_2 將棧頂引用類型(即字符串"ab")存入第三個本地變量 9: new #6 // class java/lang/StringBuilder 創(chuàng)建StringBuilder對象sb,并將引用值壓入棧頂 12: dup 復(fù)制棧頂數(shù)值,并將復(fù)制值壓入棧頂 13: invokespecial #7 // Method java/lang/StringBuilder. 調(diào)用對象的初始化方法 "<init>":()V 16: aload_0 將第一個本地變量(即字符串"a")推送至棧頂 17: invokevirtual #8 // Method java/lang/StringBuilder. 調(diào)用實例方法sb.append("a"); append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: aload_1 將第二個本地變量(即字符串"b")推送至棧頂 21: invokevirtual #8 // Method java/lang/StringBuilder. 調(diào)用實例方法sb.append("b"); append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #9 // Method java/lang/StringBuilder. 調(diào)用實例方法sb.toString(),并將結(jié)果【Java堆地址】放在棧頂 toString:()Ljava/lang/String; 27: astore_3 將棧頂引用類型(即堆地址)存入第四個本地變量 28: aload_3 將第四個本地變量(即堆地址)推送至棧頂 29: aload_2 將第三個本地變量(即字符串"ab")推送至棧頂 30: if_acmpne 37 比較棧頂兩引用數(shù)值,結(jié)果不同跳轉(zhuǎn)(當(dāng)然不同啦) 33: iconst_1 34: goto 38 37: iconst_0 將int類型 0 壓入棧頂 38: invokestatic #10 // Method java/lang/Boolean.valueO 調(diào)用靜態(tài)方法Boolean.valueOf();實現(xiàn)基本數(shù)據(jù)類型->包裝類型自動轉(zhuǎn)換 f:(Z)Ljava/lang/Boolean; 41: invokestatic #11 // Method p:(Ljava/lang/Object;)V 調(diào)用靜態(tài)方法p(false);//輸出false 44: return 從當(dāng)前方法返回void
針對其中的一些解釋:(下面的數(shù)字是字節(jié)碼偏移量)
24 為何在sb.toString()我說的是【堆地址】,大家看源碼就知道了。
//這是StringBuilder的源碼,返回的是堆上的字符串地址 public String toString() { return new String(value, 0, count); }
所以在aa()方法中,s3的內(nèi)存其實在Java堆上,s12在方法區(qū)的常量池上,所以兩者不相等。
37 boolean到底分配幾個字節(jié),在這里大家可以看到。
如果為true,編譯器翻譯的字節(jié)碼是iconst_1,意思將int類型1存入棧頂,所以單個引用boolean值時,分配4個字節(jié),和int相同。(數(shù)組boolean沒測試,不清楚)
如果為false,編譯器翻譯的字節(jié)碼是iconst_0,意思將int類型0存入棧頂。
38 在這里我們還能看到自動類型轉(zhuǎn)換的身影,這里是基本數(shù)據(jù)類型boolean->包裝類Boolean的自動類型轉(zhuǎn)換,實際調(diào)用的就是Boolean.valueOf()靜態(tài)方法,這是因為下面的p()方法里面需要的是Object引用類型,所以進(jìn)行了自動類型轉(zhuǎn)換。
然后是 bb()方法:
public static void bb(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=0 兩個本地變量空間 0: ldc #5 // String ab 將字符串"ab"從常量池中推送至棧頂 2: astore_0 將棧頂引用類型(字符串"ab")存入第一個本地變量 3: ldc #5 // String ab 將字符串"ab"從常量池中推送至棧頂 5: astore_1 將棧頂引用類型(字符串"ab")存入第一個本地變量 6: aload_0 將第一個本地變量("ab")推送至棧頂 7: aload_1 將第二個本地變量("ab")推送至棧頂 8: if_acmpne 15 比較棧頂兩引用類型數(shù)值,結(jié)果不同跳轉(zhuǎn)(這里當(dāng)然相同啦) 11: iconst_1 將int類型 1 推送至棧頂 12: goto 16 無條件跳轉(zhuǎn)到16字節(jié)碼偏移量 15: iconst_0 16: invokestatic #10 // Method java/lang/Boolean.valueO 調(diào)用靜態(tài)方法Boolean.valueOf();并將返回的Boolean類型的true壓入棧頂 f:(Z)Ljava/lang/Boolean; 19: invokestatic #11 // Method p:(Ljava/lang/Object;)V 調(diào)用靜態(tài)方法p(true);輸出true 22: return 從當(dāng)前方法返回void
針對其中的一些解釋:(下面的數(shù)字是字節(jié)碼偏移量)
0 大家看到了吧,編譯器看到String a="aa"+"bb";會自動合并,將"aabb"存入常量池,并返回地址。所以答案為true。