Java的String类记录

字符串常量池(string pool)中是否可以存放对象?

可以存放对象,从String类的本地方法intern()可以看出字符串常量池可以存放对象:

image

同时可以看出,这个方法是有返回值的。

JDK6和JDK7中,intern()方法有什么变化?

JDK7中,字符串常量池由方法区(永久代)移到了堆中,永久代也消失了,改用本地内存,改为了元空间。在JDK7和JDK8,如果以字面量的方式创建字符串实例:

String str = "hello";

那么,在字符串常量池会产生相应的对象,str指向这个实例。

如果以new的方式创建字符串对象:

String str = new String("hello");

网上的说法就开始众说纷纭。集中的一个疑问是,以这种方式创建字符串,到底堆中有没有创建对象?

String str = new String("hello");
String after_str = "hello";
System.out.println(str == after_str);

在这里,需要在println这行下个断点:

image

很意外,不管是str还是after_str,其内部final的byte[]都指向了同一个地址。那么从这个简单的例子就可以说明,首先,str和after_str不是一个对象,一个在堆中,一个在常量池中,这个应该是普遍的共识,因为上面的输出结果是false,但是数组指向的是又是同一个地址。

如果把代码顺序更换一下:

String before_str = "hello";
String str = new String("hello");
System.out.println(str + before_str);

image

数组指向的地址仍然是一样的。

参看String类构造器:

image

注意这个构造器其实还是使用了字面量的方式创建字符串,只是将字符串的相关信息又copy了一份,这个对象应该是在堆中的,结合上面的情况,可以得知其内存结构其实是下面的图示:

image

用new的方式创建字符串,其实会在堆中产生一个String类的实例,但是这个String类的byte[]指向的却是字符串常量池中的byte[]。

new String("ab")会创建几个对象?

两个,一个对象是:new关键字在堆空间创建的。另一个对象是:字符串常量池中的对象。

new String("a") + new String("b")会创建几个对象?

对象1:StringBuilder

对象2:new String("a")

对象3:常量池中的"a"

对象4:new String("b")

对象5:常量池中的"b"

深入剖析:StringBuilder的toString()方法,返回了一个新的String

对象6: new String("ab")

注意,字符串常量池中此时不存在"ab",具体原因可以参考StringBuilder的toString()方法的字节码,字节码中没有ldc:

image

同样,类似问题诸如:

String str1 = new String("ABC") + "ABC"; 在字符串常量池中创建几个实例?

答案也是1个。(ABC)

JDK7及以后intern()方法的变化

再来看这个问题:

public class Test {
    public static void main(String[] args) {
        String str1 = new String("ABC") + "ABC";
        System.out.println(str1.intern() == str1);
    }
}

首先需要认识到,"+"会调用StringBuilder的append()方法,所以常量池中在没有intern()之前,并没有"ABCABC"的任何信息

在JDK6,输出结果是false,但在JDK7,输出结果就是true了。

在JDK6中,intern()方法会在永久代创建一个"ABCABC"的对象,和堆中new出来的对象显然不是一个地址,所以是false。但JDK7将字符串常量池从永久代移到了堆中,由此发生了相应的变化,如果调用intern()方法的时候,发现常量池中没有存在堆空间中此时存在的字符串实例,就会将堆空间的地址引用存放到字符串常量池中,所以比较两端都是字符串在堆中的地址,结果是true。

总结

究其原因,产生混淆的根本在于,用new的方式创建字符串,本身也调用了通过字面量创建字符串的构造方法,所以字符串常量池中是有该对象。利用拼接操作去创建字符串(拼接的两端不都是常量的情况下),调用了StringBuilder的append()方法,是没有通过字面量创建字符串,所以字符串常量池中没有该实例,这里的逻辑和JDK版本无关。JDK7之后影响了的是intern()方法。

有一点需要注意,JDK7开始,字符串常量池可以存放引用了。

posted @ 2021-05-31 11:40  imissinstagram  Views(59)  Comments(0)    收藏  举报