Java中的字符串字面值连接是如何工作的?

3
如果我们在Java中连接两个字符串字面量,那么新的String是在何时何地创建的?
class StringClass {
    public static void main(String[] args) {

        String a = "java";
        String b = "ja";
        String c = "va";
        String d = b + c;

        System.out.println(a == d);
    }

}

如果字符串池中都包含了a和d,那为什么a == d的结果是false呢?
难道d不是字符串池的一部分吗?

2
我的猜测是,根据String#equals的定义,ad是相等的,但变量d并没有指向字符串池。 - undefined
3
顺便说一下,标题有误导,发布的代码中没有"字面串联"。它的工作原理是:编译器进行串联,并将其编译为一个单独的字面值(例如,"ja" + "va" 将与直接编码"java"相同)。 - undefined
3
你为什么在意呢?这些知识是深奥的,对于能够编写Java程序并不是必需的。只有在回答无用的琐事问题或者深入进行Java程序的高级优化时,你才需要这些知识。 - undefined
2
相关,可能重复:运行时字符串连接评估 - undefined
3
人们似乎过于纠结于“位存储在哪里”(堆?栈?文字池?),而这些都不是理解Java程序含义所必需的。语言问题,如“作用域”,则更为重要。 - undefined
显示剩余4条评论
4个回答

9
由于b + c不是一个常量表达式,它在运行时被计算。在这里不会创建字符串池条目。
d不是字符串池的一部分吗?”
正确。变量d的值尚未添加到字符串池中。只有由常量表达式生成的字符串会自动添加到字符串池中。
常量表达式在JLS 15.29中有定义。我不会在这里完整地重述它,但我鼓励您花时间阅读它。直观地说,常量表达式是可以在编译时计算的表达式,但它比这更复杂一些。 b + c不是常量表达式(在这里)的原因是bc没有声明为final。
说了这么多,其实没有必要了解字符串池的细节以及何时将某些内容添加到其中。你真正需要做的只是遵循这个“规则”:
不要使用“==”来测试字符串的相等性,而是使用String.equals(Object)方法。一直如此。
此外,不要直接使用String.intern()。在最新版本的Java中,垃圾收集器能够自动去重任何长期存在的字符串对象。如果你使用intern()来使用“==”,
这是一种微观优化,可能并不值得(因为interning和字符串池本身会产生运行时开销);而且
这是有风险的...因为你必须确保对所有要测试的字符串对象进行intern(),以避免错误的结果;而且
这会使你的代码变得更加复杂,更难理解。
实际上,垃圾回收器只是对字符串的内部支持数组进行去重。受影响的字符串仍然是不同的,所以使用==仍然不起作用。目前的去重只是为了节省空间...除了一些二级性能问题。

@user85421 这是必然的:.intern() 需要找到一个已经 interned 的字符串,该字符串与新字符串相等,为此它需要至少调用一次 equals() 方法来比较当前字符串和一个 interned 字符串(如果 intern 表类似于哈希表,则很可能还会调用 hashCode() 方法)。 - undefined
@user85421 我的印象是你想要确认a == b.intern()a.equals(b)慢,我可以很有信心地说这确实是这样。 - undefined

5
如果我们在Java中连接两个字符串字面量,那么你的代码并不是这样做的。它是在连接两个非常量变量。
String b = "ja";
String c = "va";
String d = b + c;

如果我理解正确,编译器会"看到"这个连接操作,并注意到操作数不是常量 - 所以它将连接操作留到执行时。
如果你将这些变量改为使用常量变量,通过使用final修饰符,编译器会注意到字符串连接的两个操作数都是常量表达式,然后你的代码会打印true
String a = "java";

// These are now constant variables
final String b = "ja";
final String c = "va";

// Concatenation of constant expressions
String d = b + c;

// This now prints true
System.out.println(a == d);

0
"...如果ad都是字符串池中的一部分,那么为什么a == d返回false呢?..."
一个很好的问题。
首先,如果你使用String#intern方法,它将返回true
String d = (b + c).intern();

...所有字面字符串和字符串常量表达式都会被intern。字符串字面值的定义在《Java语言规范》的第3.10.5节中...

而且,第15.29节定义了“常量表达式”

常量表达式是指一个表示原始类型值或不会突然中断并且仅由以下内容组成的字符串的表达式:...
- 加法运算符 + 和 - (§15.18) - 简单名称 (§6.5.6.1),指代常量变量 (§4.12.4)。
...
因此,您需要将 b 和 c 定义为 "常量变量"。

4.12.4. final Variables.

常量变量是一个基本类型或字符串类型的最终变量,它被一个常量表达式初始化(§15.29)。一个变量是否是常量变量可能会对类初始化(§12.4.1)、二进制兼容性(§13.1)、可达性(§14.22)和明确赋值(§16.1.1)产生影响。
因此,将bc都设为final,将导致这些值被内部化
String a = "java";
final String b = "ja";
final String c = "va";
String d = b + c;
System.out.println(a == d);

输出

true

-1
a == d返回false的原因与Java中的+运算符和字符串连接方式有关。
当使用+运算符连接两个字符串对象b和c时,Java会创建一个新的字符串对象来保存连接的结果。即使d的值与a相同,结果字符串对象d在内存中是一个不同的对象。这是因为Java为连接的结果创建了一个新的字符串对象,而不是重用原始的字符串对象。
当使用==进行比较时,它检查的是引用的相等性,由于a和d引用不同的对象,所以比较返回false。
如果你想比较a和d的内容,你应该使用.equals()方法,它基于内容检查字符串的相等性。
System.out.println(a.equals(d));

@user85421 实际上,你是对的。我纠正了我的回答。 - undefined

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