运行时字符串连接评估

时间:2012-10-14 15:37:33

标签: java

根据JLS(15.28常量表达式)表达式仅包含:

i)Literals of primitive type and literals of type String (§3.10.1, §3.10.2, §3.10.3,
§3.10.4, §3.10.5)
or
ii)Simple names (§6.5.6.1) that refer to constant variables (§4.12.4).
or
iii)...

是一个常量表达式。

现在String s1="a"+"b";是一个常量表达式,在编译时将被评估为"ab"

所以s1="ab";

[1] 我正确地说,现在根据上述声明,字符串池中有三个对象: - “a”,“b”,“ab”???

现在,

final String s="a";
final String s1="b";
String s2=s+s1;  // is also constant expression and get evaluated at compile time.

编译后,上述代码将被转换为s2="a"+"b";

所以s2="ab";将自动存储在字符串池中。

但是,

// note there is no final now.
String s="a";
String s1="b";
String s2="a"+"b";  // constant expression.
String s3=s+s1;  // is NOT a constant expression and get evaluated at RUN TIME.

对于String s3=s+s1;,代码将转换为:

s3=new StringBuilder(String.valueOf(s)).append(s1).toString();

并将创建一个新的String对象。

因此,s2==s3将会出现错误;

这是否意味着使用StringBuilder在运行时计算的字符串连接结果不会存储在字符串池中,而是进入堆(池外)?

4 个答案:

答案 0 :(得分:3)

来自JLS§15.18.1:

  

15.18.1。字符串连接运算符+

     

如果只有一个操作数表达式是String类型,则为string   转换(第5.1.11节)在另一个操作数上执行以产生a   运行时的字符串。

     

字符串连接的结果是对String对象的引用   这是两个操作数字符串的串联。那些角色   左手操作数的前面是右手的字符   新创建的字符串中的操作数。

     

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

     

实现可以选择执行转换和连接   在一步中避免创建然后丢弃中间体   字符串对象。增加重复字符串的性能   连接,Java编译器可以使用StringBuffer类或a   类似的技术减少了中间String对象的数量   通过评估表达式创建的。

     

对于原始类型,实现也可以优化掉   通过直接从基元转换来创建包装器对象   键入字符串。

所以,

  1. 常量池中有一个对象(“ab”)。暂时没有保存临时工。
  2. 同样,常量池中只有“ab”。
  3. 新字符串是一个新的String对象,除非明确实习,否则不会在池中。
  4. 查看一些字节码是有益的:

    String sa1 = "a"+ "b";
    
    final String sb1 = "a";
    final String sb2 = "b";
    String sb3 = sb1 + sb2;
    
    String sc1 = "a";
    String sc2 = "b";
    String sc3 = "a" + "b";
    String sc4 = sc1 + sc2;
    

    变为

      Code:
       0:   ldc #2; //String ab
       2:   astore_0
       3:   ldc #2; //String ab
       5:   astore_3
       6:   ldc #3; //String a
       8:   astore  4
       10:  ldc #4; //String b
       12:  astore  5
       14:  ldc #2; //String ab
       16:  astore  6
       18:  new #5; //class java/lang/StringBuilder
       21:  dup
       22:  invokespecial   #6; //Method java/lang/StringBuilder."<init>":()V
       25:  aload   4
       27:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       30:  aload   5
       32:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       35:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       38:  astore  7
       40:  return
    

    您可以看到,在前两种情况下,“ab”直接从常量池中加载。在第三个块中,我们得到翻译sc4 = new StringBuilder().append(sc1).append(sc2).toString(),它创建了一个新对象。

答案 1 :(得分:1)

  

我正确地说,现在根据上述声明,字符串池中有三个对象: - “a”,“b”,“ab”???

没有。你错了。连接在编译时执行,只有“ab”对象将存储在字符串池中。

  

这是否意味着使用StringBuilder在运行时计算的字符串连接的结果不会存储在字符串池中,而是进入堆(池外)?

是的,你在这一点上是正确的。


总之,字符串文字和编译时常量字符串表达式的值将被实现(并存储在字符串池中)。除非您明确调用String.intern(),否则其他字符串连接的结果将不会被实现。 (你应该很少这样做......因为实习字符串通常弊大于利。)

无论哪种方式,都应该避免使用==来比较字符串。

答案 2 :(得分:0)

  

这是否意味着在运行时评估字符串连接的结果   使用StringBuilder不会存储在String Pool中,而是存在   进入堆(外部池)

是。那是正确的。它将在堆中创建新对象。

答案 3 :(得分:0)

是的,你是正确的,使用StringBuilder在运行时评估的字符串不会存储在字符串池中。这是因为:

  1. 有一个new运算符(为堆中的对象分配新内存)
  2. 正如我们可以看到的那样:

    AbstractStringBuilder(int capacity){        value = new char [capacity]; }

  3. 在堆中分配了一个新的char数组引用。