扩展知识
我们知道 String 是 final 修饰的,也就是说一定被赋值就不能被修改了。但编译器除了有字符串常量池的优化之外,还会对编译期可以确认的字符串进行优化,例如以下代码:
public static void main(String[] args) { String s1 = "abc"; String s2 = "ab" + "c"; String s3 = "a" + "b" + "c"; System.out.println(s1 == s2); System.out.println(s1 == s3); }
按照 String 不能被修改的思想来看,s2 应该会在字符串常量池创建两个字符串“ab”和“c”,s3 会创建三个字符串,他们的引用对比结果也一定是 false,但其实不是,他们的结果都是 true,这是编译器优化的功劳。
同样我们使用 javac StringExample.java
先编译代码,再使用 javap -c StringExample
命令查看编译的代码如下:
警告: 文件 ./StringExample.class 不包含类 StringExample Compiled from "StringExample.java" public class com.example.StringExample { public com.example.StringExample(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #7 // String abc 2: astore_1 3: ldc #7 // String abc 5: astore_2 6: ldc #7 // String abc 8: astore_3 9: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 12: aload_1 13: aload_2 14: if_acmpne 21 17: iconst_1 18: goto 22 21: iconst_0 22: invokevirtual #15 // Method java/io/PrintStream.println:(Z)V 25: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_1 29: aload_3 30: if_acmpne 37 33: iconst_1 34: goto 38 37: iconst_0 38: invokevirtual #15 // Method java/io/PrintStream.println:(Z)V 41: return }
从 Code 3、6 可以看出字符串都被编译器优化成了字符串“abc”了。
总结
本文我们通过 javap -v XXX
的方式查看编译的代码发现 new String 首次会在字符串常量池中创建此字符串,那也就是说,通过 new 创建字符串的方式可能会创建 1 个或 2 个对象,如果常量池中已经存在此字符串只会在堆上创建一个变量,并指向字符串常量池中的值,如果字符串常量池中没有相关的字符,会先创建字符串在返回此字符串的引用给堆空间的变量。我们还将了字符串常量池在 JDK 1.7 和 JDK 1.8 的变化以及编译器对确定字符串的优化,希望能帮你正在的理解字符串的比较。