大家过年好!春节假期休了一个长假,今天刚回来。在知乎上遇到了一个很好的问题,忍不住回答了一下。原文转载过来了。
以下代码的运行结果,如何解释?
String h = new String("hw");
String h2 = h.intern();
String h1 = "hw";
System.out.println(h == h1);//false
System.out.println(h2 == h1);//true
System.out.println(h2 == h);//false
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);//true
第一,先搞清楚字符串直接量和加法运算的区别。
我们看这样一段代码:
把它编译完了以后,再使用javap -c来查看它的字节码是这样的:
看到了没有?s1直接调用了String的构造方法。但是s2不是,它实际上使用了StringBuilder,然后通过append方法把"s"和"2"串接起来,这个简单的加法实际上变成了与以下代码等价了:
第二,String的intern是什么意思?
intern方法是一个native方法,它的具体实现在hotspot的源代码里。我把它简化一下,贴上来:
看到这个代码,我们就知道了。当StringTable里没有某一个字符串的时候,调用intern的时候,就会把这个字符串添加到StringTable里去。
所以,这个代码的结果就容易理解了:
这个结果是true,就是因为intern的时候,其实就是把t1放到StringTable,并且直接把t1做为返回值赋给了t2。
第三,但是问题还没结束。字符串常量到底是怎么回事?本来这个问题快要清楚了,一出现字符串常量,一下子又复杂了。
看这样两个例子:
这个例子,按我们之前说的,h3和h是同一个对象,h3和h4是同一个对象,h和h1不是同一个对象,都可以解释了。h2实际上呢是一个字符串常量,它和h3是同一个对象好像也是对的。但我们调整一下h2的赋值,把h2放到h3之前,结果却变了:
注意,这一次,h2的赋值在前,h3在后,然后,我们看到h3和h就不再是同一个对象了。这是为啥呢?
这是因为字符串常量,在class文件的常量池中,当执行到ldc指令去访问这个常量的时候,如果该常量是一个字符串类型,hotspot就会在后面默默地创建一个字符串,并且,调用intern方法!
看到那个显眼的StringTable::intern了吗?问题就出在这里。
Java在加载字符串常量的时候会调用一遍intern,那么StringTable里就会留下这个hotspot默认创建的字符串。
好了。回到原问题。
h = new String("hw");
这条语句,"hw"是一个常量字符串,实际上,已经做过一次intern了,StringTable里保留的是hotspot默认创建的字符串。所以h2和h1会是相等的,都是StringTable里的这个默认字符串。
而s3因为是计算得来的,不是字符串常量,所以手动调用s3.intern()时,StringTable里留下的就是s3。再对s4赋值时,由于StringTable里已经有值了,所以不必再创建一次String对象,直接使用StringTable里的那个值就好了,其实就是s3,因此s3与s4是相同的对象。把s4的赋值放到s3之前再试一下。就可以验证了。
领取专属 10元无门槛券
私享最新 技术干货