奇怪的字符串池行为

我对一些奇怪的字符串池行为有疑问。 我使用 ==来比较相等的字符串以确定它们是否在池中。

public class StringPoolTest {
public static void main(String[] args) {
new StringPoolTest().run();
}


String giveLiteralString() {
return "555";
}


void run() {
String s1 = giveLiteralString() + "";
System.out.println("555" == "555" + "");
System.out.println(giveLiteralString() == giveLiteralString() + "");
}
}

输出结果是:

true
false

这对我来说是个大惊喜,有人能解释一下吗? 我认为在编译时发生了一些事情。但是为什么在字符串中添加 ""会有任何不同呢?

2368 次浏览
"555" + ""

编译时间常数

giveLiteralString() + ""

不是。因此,前者只编译成字符串常量“555”,后者编译成实际的方法调用和连接,从而产生一个新的 String 实例。


另见 JLS 3.10.5(字符串文字):

在运行时通过串联计算的字符串是新创建的,并且 因此不同。

在反编译这一行之后

System.out.println("555" == "555" + "");

我有个字节码

    LINENUMBER 8 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ICONST_1
INVOKEVIRTUAL java/io/PrintStream.println(Z)V
...

相当于

  System.out.println(true);

这意味着表达式 "555" == "555" + ""编译为布尔 true

Javac 为 giveLiteralString() == giveLiteralString() + ""构建了这个字节码

    LINENUMBER 8 L0
INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
NEW java/lang/StringBuilder
DUP
INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
IF_ACMPNE L1
...

相当于

if (giveLiteralString() == new StringBuilder(giveLiteralString()).append("").toString()) {
...

因为我们要比较两个不同的对象。

在第二种情况下,编译器可能已经认识到 + ""是排序的 no-op,因为 ""是一个已知为零长度的编译时值。但是编译器仍然需要检查来自 giveLiteralString的结果是否为 null (因为在未优化的情况下,由于 +操作的结果会发生 null 检查) ,所以最简单的做法就是不尝试优化。

因此,编译器会生成代码来执行串联,并创建一个新字符串。

编译时间串联 由常量表达式计算的字符串在编译时执行,并被视为常量或文字,意味着字符串或表达式的值在编译时已知或计算,因此编译器可以在字符串池中检查相同的值并返回相同的字符串对象引用。

运行时连接 字符串表达式的值是已知的或不能在编译时求值,但是取决于运行时的输入或条件,那么编译器将不知道字符串的值,因此总是使用 StringBuilder 来追加字符串,并且总是返回一个新的字符串。 我想这个例子会更好地阐明它。

public static void main(String[] args) {
new StringPoolTest().run();
}
String giveLiteralString() {
return "555";
}


void run() {
System.out.println("555" + 9 == "555" + 9);
System.out.println("555"+Integer.valueOf(9) == "555" + Integer.valueOf(9));
System.out.println(giveLiteralString() == giveLiteralString());
// The result of runtime concatenation is a fresh string.
System.out.println(giveLiteralString() == giveLiteralString() + "");
}