运行时字符串连接评估

根据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求值的字符串连接的结果不会存储在字符串池中,而是进入堆(池外)?


根据JLS§15.18.1:

15.18.1。 字符串连接运算符+

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

字符串连接的结果是对两个操作数字符串串联的String对象的引用。 左侧操作数的字符位于新创建的字符串中右侧操作数的字符之前。

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

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

对于基本类型,实现也可以通过直接从基元类型转换为字符串来优化封装器对象的创建。

所以,

  • 常量池中有一个对象(“ab”)。 临时文件没有保存。
  • 同样,常数池中只会有“ab”。
  • 新的字符串是一个新的String对象,除非明确实现,否则不会在池中。
  • 查看一些字节码是有益的:

    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() ,它创建一个新的对象。


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

    不,你错了。 级联在编译时执行,只有“ab”对象将被存储在字符串池中。

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

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


    总之,字符串文字和编译时常量字符串表达式的值将被实现(并存储在字符串池中)。 其他字符串连接的结果不会被拦截......除非您明确地调用String.intern() 。 (你应该很少这么做......因为实习中的琴弦通常会造成更多的伤害而不是好的)。

    无论如何,你应该避免使用==来比较字符串。


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

    是。 那是对的。 它会在堆中创建新的对象。

    链接地址: http://www.djcxy.com/p/64699.html

    上一篇: Runtime String concatenation evaluation

    下一篇: Jung coloring vertex with value