你知道 String s = new String (“abc”) 在执行时创建了几个对象吗?在回答这个问题之前,我们先来了解一下String类在JVM中的存储方式。
字符串常量池
在JVM中有一块叫做字符串常量池的存储区域,在JDK 1.6以及以前的版本中,字符串池是放在方法区,这时的方法区也加做永久代;JDK1.7的时候,方法区合并到了堆内存中,这时的常量池也可以说是在堆内存中;JDK1.8及以后,方法区又从堆内存中剥离出来了,但实现方式与之前的永久代不同,这时的方法区被叫做元空间,常量池就存储在元空间。字符串"abc"实际上就是存储在了字符串常量池中。
String对象的两种创建方式
第一种:采用字面值的方式赋值
String s1 = "abc";
第二种:采用new关键字创建对象
String s2 = new String ("abc");
当我们用“==”来测试这两种方式创建的String对象时,发现结果是false
System.out.println (s1 == s2); //false
这两种方式创建String对象是有区别的。
第一种方式是在字符串常量池中获取对象,引用s1直接指向字符串常量池中的String对象,这种方式只会在字符串常量池中创建一个String对象;
第二种方式使用了new关键字,我们知道使用new创建对象时会在堆内存中创建一个对象,所以这种方法会创建两个对象,堆内存中的对象指向了方法区内存中的对象,所以这种方式会创建两个对象(前提是"abc"并没有存在字符串常量池中),s2引用指向的是堆中的引用。
intern()方法
inter()方法是一个native方法,底层调用的是C的方法。
在JDK 6 中,当调用intern()方法的时候,如果字符串常量池先前已创建出该字符串对象,则返回常量池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。
在JDK 7 及以上中,当调用intern()方法的时候,如果字符串常量池先前已创建出该字符串对象,则返回常量池中的该字符串的引用。否则,如果该字符串对象已存在于Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在常量池中创建该字符串并返回其引用。
下面用一个例子演示一下:
String s1 = new String ("1");
s1.intern();
String s2 = "1";
String s3 = new String ("3") + new String ("3");
s3.intern();
String s4 = "33";
在JDK 6 及以下版本中:
System.out.println (s1 == s2); //false
System.out.println (s3 == s4); //false
对于s1和s2,s1指向的是字符串常量池中的String对象,s2指向的是堆中的String对象,所以两个引用指向的内存地址是不同的,结果为false;
对于s3和s4, String s3 = new String (“3”) + new String (“3”); 的执行在字符串常量池中创建了字符串对象"3",并且在堆中创建了s3引用指向的对象(内容为"33"),此时字符串常量池中是没有"33"对象的。s3.intern(); 的执行会在字符串常量池中创建"33"这个对象,s4直接指向了字符串常量池中的"33"对象,所以s3和s4所指向的内存地址是不同的,结果为false。
在JDK 7 及以上版本中:
System.out.println (s1 == s2); //false
System.out.println (s3 == s4); //true
对于s1和s2,s1引用直接指向了字符串常量池中的String对象,s2引用指向了堆中的String对象,所以两个引用指向的内存地址是不同的,结果为false;
对于s3和s4,在执行 s3.intern(); 后,与JDK 6 版本不同的是不需要在字符串常量池中创建"33",而是直接存储堆中的引用,这个引用直接指向s3引用所指向的对象,即 s3.intern() == s3 会返回true。当String s4 = “33”; 执行时会先去字符串常量池中"33",会发现已经存在,此时s4引用就是指向s3引用对象的一个引用,因此 s3 == s4 返回true。
字符串拼接
有一种特殊的字符串创建方式,那就是字符串拼接。常量与常量的拼接,结果还在常量池中,但是只要其中有一个是变量,那么拼接的结果就在堆中。如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象引用放入常量池中,并返回此对象的引用地址。
此时我们再来看本篇开始提出的问题就很简单了。
String s = new String(“abc”) 创建了几个对象?
答案是如果字符串常量池中已经存在了"abc",那么只会在堆中创建一个对象;如果字符串常量池中并没有"abc",那么会现在字符串常量池中创建"abc"对象,然后再在堆中创建一个String对象,这个对象的引用指向常量池中的"abc"。