非常关键的 intern()
当intern()方法被调用的时候,如果字符串常量池中已经存在这个字符串对象了,就返回常量池中该字符串对象的地址;如果字符串常量池中不存在,就在常量池中创建一个指向该对象堆中实例的引用,并返回这个引用地址。
字符串的创建方法
String s1 = new String("hello");
首先字符串常量池中创建一个hello,在堆中创建String对象,对象的值执向为常量池中的hello,即是hello的内存地址。(如果常量池中已有hello则复用这个)
String s2 = "hello";
几个常考题
字符数组创建字符串
在字符串常量池中创建一个hello(如果常量池中已有hello则复用这个),s2变量指向常量池中的hello。
String s3 = new String(new char[]{'h','e','l','l','o'});
String s3_1 = "hello";
System.out.println(s3==s3_1);
首先在堆中创建一个字符数组,然后new一个新的字符串对象,对象利用这个字符数组进行初始化(将这个数组做了拷贝),如果是JDK9会先将char[]转为byte[]字节数组,s3所指向的字符串对象完全存储在堆中,且其字符(字节)数组与最开始创建的字符数组是不同的。
s3_1
会引用字符串常量池中的 “hello” 字符串。在这个例子中,s3
不会引用与 s3_1
相同的 “hello” 对象。
注意点:一开始创建的字符数组是个匿名对象,并没有指向它的引用,执行完该行代码后意味着它可能会被垃圾收集器回收,但是即使这个字符数组在逻辑上可以被垃圾收集,实际的回收时机是由垃圾收集器决定的,可能会在之后的某个时间点发生。此外,对于这种小对象,垃圾收集的效果可能不是很明显,因为它们可能会留在新生代中,直到新生代被清理。
new String()不会检查字符串常量池中是否已经有相同的字符串。而是直接在堆内存中创建一个新的字符串对象。这意味着即使字符串的内容与字符串常量池中的某个字符串相同,这个新创建的字符串对象也不会引用常量池中的字符串。
字符串拼接
String s4 = new String("1") + new String("1");
String s5 = s4.intern();
String s6 = new String("1");
String s7 = s6.intern();
System.out.println(s4==s5);
System.out.println(s6==s7);
首先会创建两个字符串对象和常量池中字面量1(共用),创建StringBuilder对象,调用append()方法拼接字符串,然后toString(),实质是new String()创建一个新的字符串,但注意此时的11并不是字符串常量,没有存在字符串常量池中,s4所指向的字符串对象完全在堆中。
在对s4执行intern()方法时,由于常量池中没有"11",s4在堆中存放的"11"会被转移到常量池,并且s5会被赋予s4的引用,即此时都指向同一个字符串变量,即s4==s5。
而对于s6和s7,s6也是在堆中new的新的字符串对象,存储的值在常量池中,调用intern()直接返回常量池中"1"的引用给s7,所以s6!=s7。
字符串拼接:常量+常量
String s8 = "a"+"b";
String s9 = "ab";
System.out.println(s8==s9);
// 下面的效果是一样的
final String s8_1 = "a";
final String s8_2 = "b";
String s8 = s8_1 + s8_9;
s8和s9指向的都是常量,根据常量优化机制,编译的时候就会将"a"+"b"转为"ab"存到常量池中,所以s8和s9是相同的引用。
字符串拼接:常量+变量
String s10 = "a";
String s11 = "b";
String s12 = "a" + "b";
String s13 = s1 + s2;
System.out.println(s4 == s3);//false
字符串拼接,但凡出现一个变量,由于 s10
和 s11
是变量,Java 虚拟机不能确定它们的值在运行时是否会被改变,因此它会在堆内存中动态创建一个新的字符串对象 "ab"
,并将 s13
指向这个新创建的对象。