字符串字面值是如何创建的?

7

我正在尝试理解字符串常量池,了解字符串字面值对象在常量池中的管理方式。我无法理解为什么在下面的代码中我会得到false, 其中s2 == s4

 public static void main(String[] args) {
    String s1 = "abc";
    String s2 = "abcd";
    String s3 = "abc" +"d";
    String s4 = s1 + "d";
    System.out.println(s2 == s3); //  OP:  true
    System.out.println(s2 == s4); // OP:  false
 }

3
"abc" +"d" 是一个编译时常量表达式,而 s1 + "d" 则不是。 - johnchen902
2个回答

12
"

"abc" + "d"表达式是一个常量表达式,因此连接操作在编译时执行,导致生成等效于以下代码:

"
String s1 = "abc";
String s2 = "abcd";
String s3 = "abcd";
String s4 = s1 + "d";

表达式 s1 + "d" 不是常量表达式,因此会在执行时创建一个新的字符串对象。因此,尽管由于常量字符串池,s2s3引用同一个字符串对象,但s2s4引用不同(但相等)的字符串对象。

有关常量表达式的更多详细信息,请参见JLS第15.28节


Jon - 我已经修复了你结束语中可能的疏忽。最好检查一下这是否是你真正想表达的意思。 - Andrzej Doyle
4
请注意,如果您将s1声明为final,则第二个比较结果也将输出true,因为编译时将知道它可以被视为常量。 - Petr Janeček
1
@Slanec:说得好。我以前从未将本地变量用作常量,但它确实有效。 - Jon Skeet
真正的问题是为什么。一个简单的静态分析可以显示本地变量在任何地方都不会改变,因此该值在编译时已知。您无法从另一个线程更改s1的值(因为它是一个本地变量)。在链接的JLS文章中(以及我试图点击的其他一些文章中),没有说明编译器是否可以决定将其设置为常量。这是未定义的,还是经过深思熟虑的设计决策,不进行静态分析?如果我制作了一个编译器,在OP的情况下输出“true”,那么它是否合法? - Petr Janeček
3
@Slanec说:这并不被语言视为常量表达式。请记住调试时可能会出现的情况-您可以通过此方式更改s1,那么后面对s4的赋值不反映该更改将是奇怪的。我认为在这种情况下,简单和可预测性通常比激进的优化更好。虽然字符串连接有些实现特定,但我认为在OP的情况下输出为true至少会违反JLS的精神 - Jon Skeet
@JonSkeet 哦,没错。调试是一个很好的例子,这将会很有帮助。谢谢,现在一切都清晰了一点。在提问和回答这个问题时没有违反任何精神。 - Petr Janeček

0

s2 是在编译时创建的。为其保留内存并相应地填充。

s1 + "d" 在运行时进行评估。因为您使用了两个不同的字符串(即 s1 是一个变量,理论上可以是任何值),编译器无法预先知道您是否打算更改对象引用的值。

因此,它必须动态分配内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接