下面看一个面试题。

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