下面看一个面试题。
String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; String s5 = "a" + "b"; System.out.println(s3 == s4); System.out.println(s3 == s4);
要回答这个问题,需要搞清楚string table,先从最简单的说起,反编译下列代码。
public class Demo1_22 {
public static void main(String[] args) {
String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
}
}截取部分。可以看到jvm从#2取String a存放到了LocalVariableTable的Slot1,其他串与此类似。
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 6
line 21: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 s1 Ljava/lang/String;
6 4 2 s2 Ljava/lang/String;
9 1 3 s3 Ljava/lang/String;在jvm启动时,常量池中的内容都会加载到运行时常量池中,但是此时a,b,ab都还只是一个符号,而不是字符串对象。只有当执行到具体的指令,如0: ldc #2才会创建字符串对象"a"。于此同时,jvm还会去String table[]中去找是否有"a"这个字符串,如果没有则将其加入String table[]。注:String table[]其实是hashtable 结构,不能扩容。
在java代码中新增s4,并反编译。
String s4 = s1 + s2;
反编译结果如下。
9: new #5 // class java/lang/StringBuilder 12: dup 13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V 16: aload_1 17: invokevirtual #7 // Method java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: aload_2 21: invokevirtual #7 // Method java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore 4
以上操作等同于。
new StringBuilder().append("a").append("b").toString() 其中toString()的方法实现方式是:new String("ab")。所以s3 == s4的结果为fasle.
System.out.println(s3 == s4); //false
接着我们在代码中新增s5.
String s5 = "a" + "b";
反编译结果如下。
29: ldc #4 // String ab 31: astore 5
原来,javac编译时帮助我们进行了优化, 它认为“a”,“b”是常量,结果不可能会发生改变,于是结果直接在编译期确定为ab了。并且,由于"ab"在String table中已经存在,因此不会创建新的字符串对象了。
System.out.println(s3 == s4); //true

京公网安备 11010502036488号