1.前言
其实,促使我写这篇博客的原因说起来就挺可笑的。疫情在家,无所事事,就和室友对于专业方面的知识互相吹牛皮。然后,室友出了好几道题考我。也许结果早就在他的意料之中吧。其中,最让我不解的是这道题:
String s1 = new String("a") + new String("bc");
s1.intern();
String s2 = "abc";
System.out.println(s1 == s2); // true
这道题,我写的false。即使他告诉我答案,也给我解释了好久,我也算是勉为其难地懂了。
后来,在网上无意间又遇见它了,为了避免自己再犯错,就根据自己的理解,写下这篇博客吧。
2. 两种创建字符串对象的形式
创建字符串对象有两种形式:
- 字面量的形式。如:String s = “zzc”;
- 使用new 的形式。如:String s = new String(“zzc”);
2.1 使用字面量形式的工作原理
工作原理:
当一个.java文件被编译成.class文件时,和所有其他常量一样,每个字符串字面量都通过一种特殊的方式被记录下来。当一个.class文件被加载时,JVM在.class文件中寻找字符串字面量。当找到一个时,JVM会检查是否有相等的字符串在常量池中存放了堆中引用。如果找不到,就会在堆中创建一个对象,然后将它的引用存放在常量池中的一个常量表中。一旦一个字符串对象的引用在常量池中被创建,这个字符串在程序中的所有字面量引用都会被常量池中已经存在的那个引用代替。
举个例子:
String s = "zzc";
JVM检测这个字面量"zzc",这里我们认为没有内容为“zzc”的对象存在。JVM通过字符串常量池查找不到内容为“zzc”的字符串对象,那么就会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量s。
接上面那一条语句:
String s2 = "zzc";
同样JVM还是要检测这个字面量,JVM通过查找字符串常量池,发现内容为”zzc”字符串对象存在,于是将已经存在的字符串对象的引用返回给变量s2。注意,这里并不会重新创建新的字符串对象。
验证s与s2是否指向同一个对象
System.out.println(s == s2); // true
使用图片简单描述下:
其实,这里有一个问题:字符串常量池中存放的是对象?还是对象的引用?
推荐各位看美团官方技术团队博客的这篇文章,从这篇文章中,可以看出:JDK版本不同,字符串常量池的位置不一样,里面存放的内容也不同。JDK1.6及之前的版本,常量池是在Perm 区的,则字符串字面量的对象是直接存放在常量池中的;而JDK1.7及之后的版本,常量池中存放的是对象的引用(避免对象多次创建)。
2.2 使用new的形式
String s = "zzc";
String s2 = new String("zzc");
System.out.println(s == s2); // false
两个字符串字面量仍然被放进了常量池的常量表中,但是当使用“new”时,JVM就会在运行时创建一个新对象,而不是使用常量表中的引用
要记住引用到常量池的字符串对象是在类加载的时候创建的,而另一个对象是在运行时,当“new String”语句被执行时。
【小结】
- 字面量创建字符串会先在字符串池中找,看是否有相等的对象,没有的话就在堆中创建,把地址驻留在字符串池;有的话则直接用池中的引用,避免重复创建对象。
- new关键字创建时,前面的操作和字面量创建一样,只不过最后在运行时会创建一个新对象,变量所引用的都是这个新对象的地址。
3. intern()方法
对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern()方法。调用intern()后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。
String s = "zzc";
String s2 = new String("zzc");
String s3 = s2.intern();
System.out.println(s == s3); // true
所以说,回到我那个问题上:
String s1 = new String("a") + new String("bc");
s1.intern();
String s2 = "abc";
System.out.println(s1 == s2); // true
结果为什么是 true 呢?
String s1 = new String("a") + new String("bc");
上面那条语句会导致 字符串常量池中会有a、bc对象的引用。而s1是指向堆中对象abc的引用
s1.intern();
这条语句会导致:jvm会检测字符串常量池中是否有对象“abc”的引用,如果没有,则添加此对象的引用到常量池中去;否则,直接返回该引用。显然,这里是没有的,所以,常量池中会存一个指向堆中对象“abc”的引用。
String s2 = "abc";
执行这条语句时:jvm还是会检测常量池中是否有指向对象“abc”的引用。显然,这里是有的。所以,会把指向堆中对象“abc”的引用赋值给s2。也就是说,s2也会指向堆