为什么使用Java引用变量和文本的“+”运算符进行连接不会导致字符串插入?
考虑下面的代码片段。
案例#1
public class HelloWorld {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "ab";
str2 = str2 + "c";
System.out.println("str1 :" + str1+ ", str2 :" + str2);
System.out.println(str1 == str2);
}
}
结果是
sh-4.3$ java -Xmx128M -Xms16M HelloWorld
str1 :abc, str2 :abc
false
在这里,str1 == str2
的结果是错误的。但是,如果使用“+”运算符连接两个文本。它提供字符串常量池中字符串文字“abc”的地址。考虑下面的代码片段
案例2
public class HelloWorld {
public static void main(String[] args) {
String str1 = "abc";
//String str2 = "ab";
str2 = "ab" + "c";
System.out.println("str1 :" + str1 + ", str2 :" + str2);
System.out.println(str1 == str2);
}
}
结果是
sh-4.3$ java -Xmx128M -Xms16M HelloWorld
str1 :abc, str2 :abc
true
有人能解释一下为什么在第二种情况下进行字符串实习,而在第一种情况下不进行吗?为什么在#1的情况下'str1==str2'为假,在#2的情况下为真
# 1 楼答案
您会注意到,由于Final,编译器认为这是一个常量表达式,并在本例中对其进行了实习
# 2 楼答案
从严格意义上讲,这里已有的几个答案已经涵盖了为什么不进行实习:这不是JLS所要求的
JLS规定,interning必须发生在
"literal" + "literal"
情况下(每15.18.1),并且非常量表达式必须是新创建的(也在15.18.1中)。关于新创建的对象的相关部分似乎也为编译器或运行时保留了非文字大小写的可能性(添加了引号):在这里,我相信“有时”创建部分指的是编译器(或运行时)可以在单个表达式中选择一系列具体操作,例如
foo + 1 + anotherFoo + bar()
to新建只为最终结果创建一个字符串,但它不允许编译器或运行时对最终结果进行内部调用对所有字符串进行实习不是一个好主意的一个实际原因是,实习是一项相对昂贵的全球性业务。必须记录、检查插入的字符串的唯一性,所有这些都必须在线程之间安全,这些线程可能涉及锁或一些更快但仍然较慢的无锁逻辑。事实上,在大型应用程序中,如果不增加默认intern表的大小,
String.intern()
可能是一个重要的争用点(似乎表中每个bucket都会发生锁定)一旦以这种方式编写了需求,未来的规范就基本上不可能放松它,因为现有代码可能很容易依赖于新创建的对象::这种区别不仅通过操作符
==
很容易看出,而且在锁定对象、使用标识哈希码等时也很明显关于对位,考虑自装箱整数的行为,以及相关的静态方法,如^{} :
这里提到的缓存本质上等同于实习。在这种情况下,JLS保留了在128到127范围之外保留整数的可能性,也就是说,由实现决定。实际上,这使得开发人员对
valueOf
的使用不那么容易预测,但为实现提供了更大的灵活性,而这种灵活性实际上是在JDK 7到increase the size of the cached integer range中使用的,甚至让启动JVM的最终用户可以对其进行配置# 3 楼答案
关键的因素不是它是字符串文字,而是它是一个常量表达式。这是在JLS 15.28中定义的,它列出了获得常量表达式的所有方法。该列表包括“类型为
String
的文本”和两个字符串常量*的串联,但不包括非final
变量,即使这些变量碰巧已设置且从未更改JLS 15.28还规定了“字符串类型的常量表达式总是‘被插入’”,因此,如果某个东西是而不是常量表达式——例如,如果它包含非最终变量——那么它就不会被插入
*这表示起来有点笨拙,但基本上15.28表示,如果一个表达式只由一堆东西组成,那么它是常量,其中之一是加法运算符
+
,它对字符串执行串联——实际上没有单独的“串联I运算符”# 4 楼答案
因为JLS #3.10.5指定了字符串文本或常量字符串表达式的编译时内插,而在非常量字符串表达式的情况下不指定任何内插
也在JLS #15.28中指定