【问题标题】:String Object. Clarification needed字符串对象。需要澄清
【发布时间】:2023-03-12 16:35:02
【问题描述】:

假设我的程序中有以下行:

jobSetupErrors.append("abc");

在上面的 jobSetupErrors 是 StringBuilder() 的情况下,我看到的情况是:

  1. 新字符串对象被创建并赋值“abc”
  2. 该 String 对象的值被分配给现有的 StringBuilder 对象

如果正确,我再添加 1 行 ...

jobSetupErrors.append("abc");
logger.info("abc");

在上面的例子中,我们是否分别创建了 2 次 String 对象?

如果是这样,做这样的事情会更合适吗?

String a = "abc";
jobSetupErrors.append(a);
logger.info(a);

这是更好的方法吗?请指教

【问题讨论】:

    标签: java string stringbuilder


    【解决方案1】:

    在上面的例子中,我们创建 字符串对象分别2次?

    不,因为在 Java 中,字符串文字(双引号中的任何内容)都被保留了。这意味着这两行都指向同一个String,因此无需进一步优化。

    在您的第二个示例中,您只是创建了对同一 String 的额外引用,但这是 Java 已经为您完成的工作,方法是将对它的引用放置在称为字符串池的东西中。这在它第一次看到“abc”时发生;第二次,它检查池并发现“abc”已经存在,因此将其替换为与第一次相同的引用。

    有关字符串实习的更多信息,请参阅http://en.wikipedia.org/wiki/String_interning

    【讨论】:

      【解决方案2】:

      为了帮助找出答案,我写了一个如下所示的类:

      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 会以正确的方式自己做事,没有必要试图变得聪明 ;-)(当你想知道到底发生了什么)

      【讨论】:

      • 哎呀! ;-) 你能告诉我应该在做什么吗? ;-)
      • 如果比较代码的长度,你应该使用局部变量而不是clever中的字段;但无论如何,问题主要是关于创建字符串,从你的例子可以看出,只涉及一个字符串对象!
      • 使用局部变量,clever 仍然更长,因为您还有一个额外的astore_1 操作来将内部字符串放入变量中
      猜你喜欢
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-09
      • 2012-07-18
      • 1970-01-01
      • 2023-03-09
      相关资源
      最近更新 更多