在开始之前,我们先了解一下字符串驻留的概念。
当同时创建多个相同的字符串字面量时,只有一个字符串常量被存储在运行时常量池中。
例如:
String a = "abc";
String b = "abc";
// String c = new String("abc");
此时虽然创建了两次 String 引用,但在运行时常量池中只有一个 "abc"。对于 String c = new String("abc"),可以通过 c.intern() 获取该字符串在运行时常量池中的引用对象,因此:
String a = new String("abc");
String b = new String("abc");
System.out.println(a == b); // false
System.out.println(a.intern() == b.intern()); // true
1. String a="abc"与 String b=new String("abc")一样吗?
首先是不一样的,也不是语法糖。我们看以下代码:
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("Hello world");
String d = new String("abc");
int length = "abc".length();
}
从字节码分析可以看到,String a = "abc" 只创建了一个对象(即 "abc",位于运行时常量池)。而 String a = new String("abc") 会创建额外的对象。使用 new 关键字意味着在堆内存中也创建了一个对象。
a、b 两个对象都是通过 ldc 指令将常量 "abc" 加载进操作数栈的,所以 a == b 为 true。然而 String d = new String("abc") 会在堆中新建一个 String 对象,所以 d == b 为 false。
通常创建 String 对象时,推荐使用双引号方式,因为 new 的方式会额外创建一个对象。
2. String a="abc"要创建几个对象?
通过上面的解释可知,String a = "abc" 会创建一个对象(在运行时常量池),而 String a = new String("abc") 会创建两个对象(一个在运行时常量池的 "abc",一个在堆里面)。需要注意的是,这是在两者不同时出现的情况下。
3. String a="abc"; String b=new String("abc") 又要创建几个对象?
理解上述内容后便容易解答。当 String a = "abc" 与 String b = new String("abc") 同时出现时,总共会创建 2 个对象。
首先
String a = "abc"在运行时常量池中创建了一个对象。当执行String b = new String("abc")时,由于运行时常量池中已存在对象"abc",则不需要在常量池中新建,只需在堆中创建一个对象即可。
4. String a="abc"; String b="abc"; 那么 a==b 吗?
结果为 true。正如前文所述,a 与 b 都指向了运行时常量池中的 "abc"。
5. String a="hello"+"world", 创建几个对象?String b="helloworld", a==b 吗?
这里涉及常量折叠概念。这是在早期编译阶段的一种优化。例如:
int a = 1 + 2;
经过常量折叠后,1、+、2 变成了字面量 3,所以在代码中定义 int a = 1 + 2 并不比 int a = 3 增加程序运行期的运算量。
相应的,"hello"、"world" 也是常量,经过常量折叠后与 String b = "helloworld" 无异。所以 a == b 为 true。因此 String a = "hello" + "world" 也只会产生一个对象。
查看字节码验证:
public class Main {
public static void main(String[] args) {
String a = "helloWorld";
String b = "hello" + "World";
System.out.println(a == b);
}
}
使用 javap -verbose Main 查看,只有合并后的常量,而没有单独的 hello 和 world。且 ldc 加载的常量索引相同,说明 String a = "helloworld" 与 String b = "hello" + "world" 没有区别。
String c=new String("hello"+"world") 创建了几个对象?
由于经过常量折叠,"hello" + "world" 与 "helloworld" 没有区别,所以:
String c = new String("hello" + "world");
与
String c = new String("helloworld");
效果一致,String c = new String("hello" + "world") 也只产生两个对象(一个常量池,一个堆)。
6. 下面程序创建几个对象?特别注意 String b=new String("hello")+new String("world") 产生几个对象?
String a = "hello" + "world";
String b = new String("hello") + new String("world");
对于 String a = "hello" + "world",肯定只创建一个对象。
而对于 String b = new String("hello") + new String("world"),会产生 7 个对象:
"hello"与"world"两个在运行时常量池的对象。new String("hello")与new String("world")两个在堆中的对象。- Java 8 中字符串拼接使用
StringBuilder实现,需要创建一个StringBuilder对象。 StringBuilder内部有一个char[]数组,数组本身也是一个对象。- 调用
StringBuilder对象的toString()方法时,又会new一个String对象。
综上,共产生 7 个对象。
查看 StringBuilder.append(String) 源码:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
它调用了父类方法 super.append(str):
public AbstractStringBuilder append(String str) {
if (str == null) return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count); // 这里的 value 是一个 char[] 数组
count += len;
return this;
}
看到 value 了吗?它是一个 char[] 数组,数组也算对象:
/**
* The value is used for character storage.
*/
char[] value;
至此第 6 个对象找到了。再看第 7 个对象,随后调用 toString() 方法:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count); // 这里产生了一个对象
}
至此第 7 个对象就出现了。


