8

根据 JLS(15.28 Constant Expressions) 的表达式仅包含:

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)...

是一个常数表达式。

NowString 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

4 回答 4

3

来自 JLS §15.18.1:

15.18.1。字符串连接运算符 +

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

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

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

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

对于原始类型,实现还可以通过直接从原始类型转换为字符串来优化包装对象的创建。

所以,

  1. 常量池(“ab”)中有一个对象。临时对象没有保存。
  2. 同样,常量池中也只有“ab”。
  3. 新字符串是一个新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”是直接从常量池中加载的。在第三个块中,我们得到了 translate sc4 = new StringBuilder().append(sc1).append(sc2).toString(),它创建了一个新对象。

于 2012-10-14T15:44:59.800 回答
1

我说得对吗,根据上面的说法,现在字符串池中有三个对象:-“a”,“b”,“ab”???

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

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

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


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

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

于 2012-10-14T15:44:49.107 回答
0

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

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

于 2012-10-14T15:44:16.173 回答
0

是的,您是正确的,在运行时使用 StringBuilder 评估的字符串没有存储在字符串池中。因为:

  1. 有一个new操作符(为堆中的对象分配新内存)
  2. 正如我们从代码中看到的:

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

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

于 2012-10-14T15:45:30.620 回答