字符串双引号、new String("")、intern()方法的总结
写本文的原因是因为看到了下面这些面试的题目,有点晕,故找了几篇博客学习了一下,以下是代码和学习记录
代码
String s1 = new String("1")+new String("2");
s1.intern();
String s2 = "12";
System.out.println(s1==s2); // true
String s1 = new String("12");
s1.intern();
String s2 = "12";
System.out.println(s1==s2); // false
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
//常量池上存在常量AABB
String AABB = "AABB";
String AA_BB = "AA" + "BB";
System.out.println(AABB == AA_BB); //true
String s2 = "a";
s2+= "b";
String s1 = "ab";
System.out.println(s1 == s2); // false
学习记录与解释
创建字符串分析
一、直接使用双引号创建字符串
直接使用双引号创建字符串会经历以下几个步骤:
- 判断这个字符串常量是否存在于常量池
- 如果存在
- 返回常量池中对象的地址
- 如果不存在
- 在常量池中创建该常量对象,并返回此常量对象的地址
String a1 = "AA";//在常量池上创建常量AA
String a2 = "AA";//直接返回已经存在的常量AA
System.out.println(a1 == a2); //true
// 从上述逻辑可以看出,a1和a2都是常量池中字符串常量"AA"对象的地址。
String a3 = new String("AA"); //在堆上创建对象AA、在常量池中创建字符串常量"AA"
a3.intern(); //返回常量池上字符串常量"AA"的引用
String a4 = "AA"; // 字符串常量存在于常量池,返回字符串常量"AA"的地址。
System.out.println(a3 == a4); //false,如果这个例子不理解,请看完整篇文章再回来看这里
二、new String创建字符串
直接使用new String创建字符串会经历以下几个步骤:
- 首先在堆上创建对象(无论堆上是否存在相同的字面量的对象)
- 判断常量池中是否存在相同字符串的字面量
- 如果存在
- 啥也不做
- 如果不存在
- 在常量池中创建常量
String a1 = new String("AA");
String a2 = new String("AA");
System.out.println(a1 == a2); //false
// 很显然,a1、a2都是堆上各自的对象地址,所以返回false
//如果常量池上不存在常量AA,也不存在引用AA,则创建常量AA
String a1 = new String("AA");
System.out.println(a1 == a1.intern()); //false
// a1是堆上"AA"对象的地址,a1.intern()是字符串常量池中对象的地址,所以返回false
三、双引号相加
使用双引号相加会经历以下几个步骤:
- 如果两个相加的都是常量。例如“a” + “b”
- 判断相加后的常量在常量池上是否存在
- 如果不存在
- 在常量池上创建相应的常量
- 如果存在
- 返回常量池中对应对象的引用
- 如果相加的有一个是变量,例如 String b = “b”;String c = “a” + b;
- 判断这两个常量和相加后的常量在常量池上是否存在
- 如果不存在
- 在常量池上创建相应的常量
- 如果存在
- 返回常量池中对应对象的引用
String a2 = "AABB";//常量池上存在常量AABB
String a3 = "AA" + "BB";// 并返回"AABB"常量的地址
System.out.println(a2 == a3); //true
//常量池上存在引用AABB
String a4 = new String("AA") + new String("BB"); //在堆上创建对象AA、BB和AABB,在常量池上创建常量AA和BB
a4.intern(); // 将堆中"AABB"对象地址保存在常量池中
String a5 = "AA" + "BB";
System.out.println(a4 == a5); //true||如果注释a4.intern()将为false
String a = "AA" + "BB";
String a0 = new String("A") + new String("A");
String a1 = a0.intern(); // // a1地址是堆上“AA”的地址
System.out.println(a1 == a0); // true
String b = "BB";
String a = "AA" + b;
String a0 = new String("A") + new String("A");
String a1 = a0.intern(); // a1是常量池中“AA”地址,a0是堆中“AA”的地址
System.out.println(a1 == a0); // false,因为“AA”是在创建变量a时就已经创建了。
四、两个new String相加
- 创建两个对象和相加后的对象
- 判断常量池中是否存在这两个对象的字面量常量(注意没有判断相加后的对象是否存在)
- 如果存在
- 啥也不做
- 如果不存在
- 在常量池上创建对应的字符串常量
// 在堆中创建"AA","BB","AABB",在常量池中创建"AA","BB"
String a2 = new String("AA") + new String("BB");
// 在堆中创建"A","A","AA",在常量池中创建"A"
String a3 = new String("A")+new String("A");
System.out.println(a3 == a3.intern()); //false
// 因为a3是在堆中"AA"对象的地址,a3.intern()是上一步中在常量池中创建的"AA"所以不同
//只在堆上创建AABB对象,没有在常量池中创建常量AABB
String a2 = new String("AA") + new String("BB");
System.out.println(a2 == a2.intern()); //true
// 由于没有在常量池中创建常量AABB,所以在a2.intern()的时候,会将a2的地址保存在常量池并返回,所以为true
五、双引号字符串与new String字符串相加
- 创建两个对象,一个是new String的对象,一个是相加后的对象
- 判断双引号常量与new String的字面量在常量池是否存在
- 如果存在
- 不做操作
- 如果不存在
- 在常量池上创建对象的常量
String a1 = "AABB";
String a2 = "AA" + new String("BB");
System.out.println(a1 == a2.intern());//true
System.out.println(a2 == a2.intern()); //false
String.intern()分析
执行该函数会执行以下步骤
- 判断这个常量是否存在于常量池
- 如果存在
- 返回对象的引用
- 如果不存在
- 将当前对象的引用复制到常量池,并且返回的时当前对象的引用
总结
一、只在常量池上创建对象
String a1 = "AA";
二、只在堆上创建对象
String a2 = new String("A") + new String("A");
// 只在堆上创建了"AA"。但是还是在堆上和常量池中创建了"A",并且堆中创建了两个"A"
三、在堆上创建对象,在常量池上创建常量
String a3 = new String("AA");
四、在堆上创建对象,在常量池上创建引用
String a4 = new String("A") + new String("A");//只在堆上创建对象AA
a4.intern();//将该对象AA的引用保存到常量池上
题目分析
String s2 = "a";// 在常量池中创建"a",并将引用赋值给s2
s2+= "b"; // 注意,与变量相加的时候是在运行期堆中运行的。所以会在常量池中创建"b",在堆中创建"ab"
String s1 = "ab";// 注意这里的"ab"是在常量池中的
System.out.println(s1 == s2); // 所以会返回false