在Java中,String s1 = new String("abc");
这句代码实际上创建了两个字符串对象,尽管从表面上看它似乎只创建了一个。
- 字面量字符串
"abc"
:- 当JVM在代码中遇到字符串字面量
"abc"
时,它首先会检查字符串常量池中是否已经存在内容为"abc"
的字符串对象。 - 如果不存在,JVM会在字符串常量池中创建一个新的字符串对象,内容为
"abc"
,并返回该对象的引用(但实际上,在这行代码执行之前,我们并没有直接使用这个引用)。 - 如果已经存在,JVM则不会创建新的对象,而是直接返回常量池中已存在对象的引用。但在这个特定的情况下,由于这是第一次出现
"abc"
,所以会在常量池中创建一个新的对象。
- 当JVM在代码中遇到字符串字面量
- 通过
new
关键字创建的字符串对象:new String("abc")
会在堆内存中创建一个新的String
对象。这个新创建的String
对象的内部会包含对字符串常量池中"abc"
字符串对象的引用(或者是其内容的副本,具体取决于JVM的实现,但在大多数情况下,为了节省内存,会是对常量池中对象的引用)。- 重要的是要理解,尽管这个新创建的
String
对象的内容与常量池中的"abc"
相同,但它们是两个不同的对象,分别存储在堆内存和常量池中。
因此,String s1 = new String("abc");
这句话实际上创建了两个对象:一个在字符串常量池中(如果它之前不存在的话),另一个在堆内存中。s1
变量引用的是堆内存中的那个 String
对象。
public class StringExample {
public static void main(String[] args) {
String s1 = new String("abc");
}
}
对应的字节码:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: invokespecial #3 // Method java/lang/String."<init>":(Ljava/lang/String;)V
5: astore_1
6: return
分析字节码指令:
0: ldc #2 // String abc
- 这条指令将字符串常量池中的
"abc"
字符串的引用推送到操作数栈上。如果"abc"
之前不在常量池中,JVM会在这一刻将其添加到常量池中。但请注意,ldc
指令本身并不在字节码层面“创建”对象;它只是引用了一个已经存在(或即将被创建)的常量池条目。
- 这条指令将字符串常量池中的
2: invokespecial #3 // Method java/lang/String."<init>":(Ljava/lang/String;)V
- 这条指令调用了
String
类的构造函数,该构造函数接受一个字符串参数(在这个例子中,是操作数栈顶部的"abc"
字符串的引用)。这导致在堆上创建了一个新的String
对象,该对象的内容与常量池中的"abc"
相同,但它们是两个不同的对象。
- 这条指令调用了
5: astore_1
- 这条指令将操作数栈顶部的引用(即新创建的
String
对象的引用)存储到局部变量表的第一个槽位中,该槽位对应于s1
变量。
- 这条指令将操作数栈顶部的引用(即新创建的
6: return
- 这条指令表示方法结束并返回。
虽然字节码本身不直接说明“创建了两个对象”,但通过分析我们可以推断出,在执行String s1 = new String("abc");
时,JVM首先(在需要时)在字符串常量池中创建或引用了一个"abc"
字符串对象,然后在堆内存中创建了一个新的String
对象,其内容引用(或复制自,取决于JVM实现)常量池中的"abc"
字符串。因此,从逻辑上讲,这句话确实创建了两个字符串对象。