字符串对象。需要澄清。

4

假设我在程序中有以下代码行:

jobSetupErrors.append("abc");

在上述情况中,当jobSetupErrors是一个StringBuilder()时,我看到的情况是:
  1. 创建一个新的字符串对象并赋值为"abc"
  2. 将该字符串对象的值分配给现有的StringBuilder对象
如果这是正确的,并且我再添加1行...
jobSetupErrors.append("abc");
logger.info("abc");

在上面的例子中,我们是分别创建了两个字符串对象吗?
如果是这样,那么像这样做是否更为适当?
String a = "abc";
jobSetupErrors.append(a);
logger.info(a);

这是更好的方法吗?请建议。
2个回答

8
在上面的示例中,我们是否分别创建了2个String对象?
不是的,因为在Java中,String字面量(双引号中的任何内容)是被interned的。这意味着这两行都指向同一个String,因此不需要进一步优化。
在您的第二个示例中,您只是创建了对同一String的额外引用,但这就是Java已经通过将对其的引用放置在称为字符串池的东西中为您完成的。它在第一次看到"abc"时发生;第二次,它检查池并发现"abc"已经存在,因此用与第一个相同的引用替换它。
有关String intern的更多信息,请参见http://en.wikipedia.org/wiki/String_interning

4
为了帮助解决问题,我编写了以下类的代码:
class Test {
  String a = "abc" ;
  StringBuilder buffer = new StringBuilder() ;

  public void normal() {
    buffer.append( "abc" ) ;
    buffer.append( "abc" ) ;
  }

  public void clever() {
    buffer.append( a ) ;
    buffer.append( a ) ;
  }
}

如果我们编译这个程序,然后运行javap命令来提取字节码:
14:09:58 :: javap $ javap -c Test
Compiled from "Test.java"
class Test extends java.lang.Object{
java.lang.String a;

java.lang.StringBuilder buffer;

Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   ldc #2; //String abc
   7:   putfield    #3; //Field a:Ljava/lang/String;
   10:  aload_0
   11:  new #4; //class java/lang/StringBuilder
   14:  dup
   15:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
   18:  putfield    #6; //Field buffer:Ljava/lang/StringBuilder;
   21:  return

public void normal();
  Code:
   0:   aload_0
   1:   getfield    #6; //Field buffer:Ljava/lang/StringBuilder;
   4:   ldc #2; //String abc
   6:   invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   9:   pop
   10:  aload_0
   11:  getfield    #6; //Field buffer:Ljava/lang/StringBuilder;
   14:  ldc #2; //String abc
   16:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   19:  pop
   20:  return

public void clever();
  Code:
   0:   aload_0
   1:   getfield    #6; //Field buffer:Ljava/lang/StringBuilder;
   4:   aload_0
   5:   getfield    #3; //Field a:Ljava/lang/String;
   8:   invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  pop
   12:  aload_0
   13:  getfield    #6; //Field buffer:Ljava/lang/StringBuilder;
   16:  aload_0
   17:  getfield    #3; //Field a:Ljava/lang/String;
   20:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   23:  pop
   24:  return

}

我们可以看到两件事情。
首先,"normal"方法对于这两个调用使用相同的String实例(确实是与初始化块中设置为该类的"a"成员变量相同的文字)。
其次,"clever"方法比"normal"方法更长。这是因为需要额外的步骤来从类中获取属性。
所以故事的寓意是,99%的时间,Java会自己以正确的方式处理事情,没有必要尝试聪明;-) (当您想要了解发生了什么时,javap是一个非常酷的工具)。

哎呀!;-)你能告诉我我应该在做什么吗?;-) - tim_yates
如果要比较代码的长度,应该在clever中使用局部变量而不是字段;但无论如何,问题主要是关于创建字符串的,正如您的示例所示,只涉及一个字符串对象! - user85421
使用局部变量时,clever 仍然更长,因为您需要执行额外的 astore_1 操作将 interned 字符串存储到变量中。 - tim_yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接