计算由Java代码创建的String对象

以下代码创建了多少个String对象?

String x = new String("xyz"); String y = "abc"; x = x + y; 

我访问了很多网站,其中一些人说这行代码创建了3个对象,有些人说它创建了4个。我只是想知道在执行这行代码后创建了多少个对象。

在运行结束时,将有四个String对象:

  1. 与实习"xyz"文字对应的String
  2. 它的副本由new String("xyz")
  3. 与实习"abc"文字对应的String
  4. 对应于串联"xyz" + "abc" String

真正的问题是部分或全部这些对象归因于您的程序。 可以合理地声称您的代码只创建了两个或多达四个String 。 尽管总共有四个String对象,但对象1和3可能不一定由代码创建,因为它们位于常量池中,因此它们是在代码的直接控制之外创建的。

这个答案是纠正一些其他答案所提出的误解:

例如:

但编译器可能会用x + y替换常量(“xyzabc”)。 @Binkan Salaryman

…和String对象4 [对应于串联的字符串]可以由编译器计算并转换为实习常量。 @dasblinkenlight

这是不正确的。 JLS声明:

15.18.1。 字符串连接运算符+

….

除非表达式是常量表达式(第15.28节),否则新创建String对象(第12.5节)。

为了限定为常量表达式,表达式中的变量名必须为:

引用常量变量的简单名称(第6.5.6.1节)(§4.12.4)。

其中“常量变量”定义为:

常量变量是基本类型的最终变量或用常量表达式初始化的String类型(第15.28节)。

在此示例中, xy都不是final因此它们不是常量变量。 即使它们是final ,由于在初始化中使用new运算符, y 仍然不会是常量变量。


简而言之,Java编译器不允许使用内联常量"xyzabc"作为串联表达式的结果。

如果我在最后添加以下声明:

  System.out.println(x == "xyzabc"); 

它将始终打印false …假设编译器符合Java语言规范。

看看反编译的类,你会看到一切:)答案应该是:

  • 两个字符串( "xyz""abc" )仅引用常量池中的位置,因此这些不是由您的代码创建的
  • 直接创建一个字符串( new String("xyz")
  • 字符串连接由编译器优化并更改为StringBuilder,因此最后一个字符串是间接创建的

     public java.lang.String method(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=1 0: new #2 // class java/lang/String 3: dup 4: ldc #3 // String xyz 6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V 9: astore_1 10: ldc #5 // String abc 12: astore_2 13: new #6 // class java/lang/StringBuilder 16: dup 17: invokespecial #7 // Method java/lang/StringBuilder."":()V 20: aload_1 21: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: aload_2 25: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 28: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 31: astore_1 32: aload_1 33: areturn 

答案是4

当您使用new关键字时,Java将在普通(非池)内存中创建一个新的String对象, x将引用它。 除此之外,文字“xyz”将被放置在字符串池中,这又是另一个字符串对象。

所以,4个字符串对象是:

  1. “xyz” (在非池内存中)
  2. “xyz” (在池内存中)
  3. “abc” (在游泳池记忆中)
  4. “xyzabc” (在非池内存中)

如果您的代码是这样的:

 String x = "xyz"; String y = "abc"; x = x + y; 

然后答案是3

注意:字符串#4位于非池内存中,因为字符串文字和通过计算常量表达式生成的字符串(请参阅JLS§15.28)是唯一隐式实现的字符串。

资料来源:SCJP Sun认证Java 6程序员(页码:434,第6章)

如果要测试实例,请运行此代码段并查看输出:

 import static java.lang.System.identityHashCode; public class Program { public static void main(String... args) { String x = new String("xyz"); String y = "abc"; String z = x + y; System.out.printf("x: %d | %d\n", identityHashCode(x), identityHashCode(x.intern())); System.out.printf("y: %d | %d\n", identityHashCode(y), identityHashCode(y.intern())); System.out.printf("z: %d | %d\n", identityHashCode(z), identityHashCode(z.intern())); } } 

我使用jdk1.7.0_67输出以下内容

x:414853995 | 1719175803
y:1405489012 | 1405489012
z:1881191331 | 1881191331

这总共有4个String实例……

new String(”xyz“)肯定会创建一个新实例。 "abc""xyz"存储在类常量池中, x = x + y在引擎盖下创建一个StringBuilder ,因此创建一个new String ,因此这里的字符串数为4。


但编译器可能会用x + y替换常量( "xyzabc" )。

4因为:

  • x + y将由具有StringBuilder实现的编译器替换。 +1
  • new总是会创建一个新对象。 +1
  • 不同的常量是不同的对象。 +2

就是这样:

 String x = new String("xyz"); // 2 objects created: the variable and the constant String y = "abc"; // 1 object created: the variable x = x + y; // 1 object created: the one by the StringBuilder class 

你看到问题的不同答案的原因是(部分)它是模棱两可的。

“以下代码创建了多少个String对象?”

模糊性在短语“由以下代码创建”中:

  • 是否询问(仅)执行代码创建的String对象的数量?

  • 或者……是否询问在执行代码期间需要存在的String对象的数量?

您可以争辩说,代码隐式创建了与文字对应的String对象,而不是在运行时,而是在加载时。 但是,这些对象可以与使用相同文字的其他代码共享。 (如果你看起来很难,那么在类加载过程中可能会创建包含相同字符串的其他字符串对象。)


您看到不同答案的另一个原因是,创建的字符串数量并不完全清楚。 各种规范声明在某些点创建新的String对象,但是关于是否可以创建中间String对象存在“蠕动空间”。

例如,JLS声明new总是创建一个新对象,并且字符串连接运算符创建一个新对象,除非在某些明确指定的情况下(参见我的其他答案)。 但是,规范并不禁止在幕后创建其他字符串。


但在这种情况下,如果我们假设我们使用的是现代Hotspot JVM,那么:

  • 在代码开始执行之前,存在2个字符串对象(用于字符串文字),和
  • 执行代码时,将创建2个新的字符串对象(由new+运算符)。

JLS保证了这4个字符串的存在/创建。

答案是4

 String x = new String("xyz");//First Object String y = "abc";//Second Object x = x + y;//Third, fourth Object 

有时最好让字节代码说话使用JAVAP检查代码

 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: new #16 // class java/lang/String 3: dup 4: ldc #18 // String xyz 6: invokespecial #20 // Method java/lang/String."":(Ljava/lang/String;)V 9: astore_1 10: ldc #23 // String abc 12: astore_2 13: new #25 // class java/lang/StringBuilder 16: dup 17: aload_1 18: invokestatic #27 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 21: invokespecial #31 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V 24: aload_2 25: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/St ringBuilder; 28: invokevirtual #36 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 31: astore_1 32: return LineNumberTable: line 6: 0 line 7: 10 line 8: 13 line 9: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 10 23 1 x Ljava/lang/String; 13 20 2 y Ljava/lang/String; } 

现在从代码中可以看出

 At `0: new` Creates a new String Object At `3:dup` ; make an extra reference to the new instance At `4:ldc #18` as seen literal "xyz" has been placed in the pool (one string Object) At `6: invokespecial;` ; now call an instance initialization method with parameter and creates a object in nonpool memory. At `9: astore_1` Stores the above reference in local variable 1(ie x) 

所以到这个时候我们有两个String对象

 At `10:ldc #23` as seen literal "abc" has been placed in the pool (third string ) At `12: astore_2` Stores the above reference in local variable (ie y) 

所以到这个时候我们有三个String对象

  28: invokevirtual #36 // Method java/lang/StringBuilder.toString: ()Ljava/lang/String;;(fourth String Object is Created) 

所以我们在这段代码中总共有四个String Object。

因为我是编程的新手,并且已经开始学习,仅仅几个月后,如果我在某个地方出错了,请指出我,它的正确版本是什么。 谢谢 :)

 Line 1:String x = new String("xyz"); Line 2:String y = "abc"; Line 3:x = x + y; 

字符串是不可变的,因此如果需要更改任何现有的字符串变量,则将创建新对象以进行分配。 第1行,第2行是字符串对象,其中第3行是对现有字符串变量的修改,因此需要进行新的分配以添加x + y。 所以它应该创建创建3个对象。

1.在堆区域“xyz”中创建的对象//由’String x’和xyzabc创建//由’x + y’创建(连接)

2.在scp(字符串常量池)中创建的对象“xyz”//为了将来的目的而创建,不可用于垃圾收集,而“abc”//由’String y’文字创建

所以在这种情况下创建的总对象是4