每个人都知道Java的String对象是不可变的,这本质上意味着如果你把String对象a
和另一个String对象b
连接起来,一个全新的String对象将被创建,而不是原地连接。
然而,最近我在SO上读到这个问题,它告诉编译器将连接操作符+
替换为StringBuilder
实例。
StringBuilder
对象是可变的,由于他们的内部结构。在这种情况下,这不会本质上使Java中的String对象可变吗? 不完全是。
实际的String仍然是不可变的,但是在编译时,JVM可以检测到一些情况,其中额外String对象的创建可以由StringBuilder代替。
所以如果你声明一个字符串a
并与另一个字符串连接,你的a
对象不会改变,(因为它是不可变的),但是JVM通过用StringBuilder的实例化取代连接来优化这一点,将两个字符串附加到Builder中,最后分配结果字符串。
假设你有:
String a = "banana";
String d = a + "123" + "xpto";
在JVM对其进行优化之前,您实际上会为如此简单的内容创建相对大量的string,即:
- 字符串
- 字符串"123"
- 字符串"xpto"
- 字符串a + "123" +
- 字符串"123"+"xpto"
通过将串接转换为StringBuilder的优化,JVM不再需要创建串接的中间结果,因此只需要单个字符串和结果字符串。
这样做基本上是出于性能原因,但请记住,在某些情况下,如果不小心,您将为此付出巨大的代价。例如:
String a = "";
for(String str: listOfStrings){
a += str;
}
如果您正在做这样的事情,JVM将在每次迭代中实例化一个新的 StringBuilder,如果listOfStrings
有很多元素,这将是非常昂贵的。在这种情况下,您应该显式地使用StringBuilder
,并在循环内执行追加,而不是连接。
字符串是不可变的对象。一旦将它与另一个String连接起来,它就变成了一个新对象。记住,你不需要改变现有的字符串,你只需要创建一个新的
字符串确实是不可变的。编译器可能会生成涉及StringBuilder
的代码,但这只是一个优化,不会改变代码的行为(除了性能)。如果在某些情况下,你可以观察到突变(例如,通过保持对其中一个中间结果的引用),编译器将不得不以一种仍然为该中间引用提供不可变字符串的方式进行优化。
所以如果StringBuilder
在幕后使用,即使你不能直接看到差异,这是否仍然意味着涉及到可变性?嗯,是的,但是如果你仔细想想,你的电脑里所有的内存都是可变的。不可变对象的内存可以被垃圾收集器移动,这肯定也涉及到突变。最后,作为一名程序员,重要的是这种突变对你是隐藏的,你得到了一个很大的承诺,你的程序将按照你期望的方式运行,也就是说,你永远不会在不可变对象中看到突变(除非出现严重的问题,例如RAM故障)。