Java不可变字符串的困惑

5
如果Java中的字符串是不可变的,那么我们如何编写以下代码: ```java String str = "Hello"; str = str + " World"; ```
String s = new String();
s = s + "abc";

1
s不是字符串,它是一个容器,用于保存对字符串的引用。首先,它保存了由new String()返回的字符串的引用,然后它被更改为保存由new String() + "abc"返回的字符串的引用,即另一个字符串的引用。 - ignis
8个回答

8

字符串是不可变的。这意味着String的实例不可以改变。

你正在将s变量更改为引用另一个(但仍然不可变的)String实例。


7

你的字符串变量并不是字符串本身,它是指向 String 实例的引用。

看看这个例子:

String str = "Test String";
System.out.println( System.identityHashCode(str) ); // INSTANCE ID of the string

str = str + "Another value";
System.out.println( System.identityHashCode(str) ); // Whoa, it's a different string!

str变量指向的实例是不可变的,但是这个变量可以指向任何你想要的String实例。

如果你不希望str可以被赋值为指向不同的字符串实例,那么声明它为final:

final String str = "Test String";
System.out.println( System.identityHashCode(str) ); // INSTANCE ID of the string

str = str + "Another value"; // BREAKS HORRIBLY

简单点儿。str.toString() 返回字符串本身。调用 ((Object)str).toString() 将会返回实例的 ID。这只是为了演示目的。 - Gabriel Bauman
2
不会。Java 中的方法是虚拟的,将对象强制转换为超类型在这里没有影响。String 的重写 toString() 方法实现仍将被调用。您需要调用 System.identityHashCode(str) 以获取对象的原始基于身份的哈希码。 - Natix
啊!谢谢你的提示。我应该在发布代码之前先尝试一下。我会调整帖子以反映这点。 - Gabriel Bauman

2
第一个答案是绝对正确的。您应该将其标记为已回答。 s = s + "abc" 不会将内容附加到 s 对象中,而是创建一个新字符串,其中包含 s 对象中的字符(没有任何字符)和 "abc"。
如果字符串是可变的,它将具有诸如 append() 等变异方法,类似于 StringBuilder 和 StringBuffer 上的方法。
Josh Bloch 的《Effective Java》对不可变对象及其价值有很好的讨论。

1

不可变类是那些其方法可以改变其字段的类,例如:

Foo f = new Foo("a");
f.setField("b"); // Now, you are changing the field of class Foo

但在不可变类中,例如String,一旦创建对象就无法更改,但是当然可以将引用重新分配给另一个对象。例如:

String s = "Hello";
s.substring(0,1); // s is still "Hello"
s = s.substring(0,1); // s is now referring to another object whose value is "H"

0

只是为了澄清,当你说 s = s+"abc" 时; 这意味着创建一个新的字符串实例(由 s 和 "abc" 组成),然后将该新的字符串实例分配给 s。因此,s 中的新引用与旧引用不同。

请记住,变量实际上是对某个特定内存位置上的对象的引用。即使您将变量更改为引用不同位置上的新对象,该位置上的对象仍保留在原地。


0
   String s = new String();  

创建一个新的、不可变的、空字符串,变量“s”引用它。

   s = s+"abc";              

创建一个新的、不可变的字符串;空字符串和"abc"的连接,变量"s"现在引用这个新对象。


0
String s = new String();

创建了一个空的String对象(""),并且变量s指向该对象。
s = s + "abc";

"abc" 是一个字符串字面量(实际上是一个 String 对象,它会被隐式地创建并保存在字符串池中),以便可以重复使用(因为字符串是不可变的,因此是常量)。但是当你执行 new String() 时完全不同,因为你是显式地创建对象,所以不会最终出现在池中。你可以通过一种叫做 intern 的方法将其放入池中。

因此,s + "abc" 在这个点上,连接空字符串 ("") 和 "abc" 并不会真正创建一个新的 String 对象,因为最终结果是已经存在于池中的 "abc"。因此,最终变量 s 将引用池中的字面量 "abc"


-2

我相信你们都把这个问题想得太复杂了,这只会让那些试图学习的人感到困惑!

在Java中使对象不可变的主要好处是可以通过引用传递(例如传递给另一个方法或使用赋值运算符分配)而无需担心对象的下游更改会导致当前方法或上下文中出现问题。(这与任何关于对象线程安全性的对话非常不同。)

为了说明这一点,请创建一个应用程序,将一个字符串作为参数传递给一个单独的方法,并在该方法中修改该字符串。在调用方法结束后打印字符串,然后在控制返回到调用方法后再次打印字符串。这两个字符串将具有不同的值,这是因为它们指向不同的内存位置,这是“更改”不可变字符串的直接结果(在幕后创建一个新指针并将其指向新值)。然后创建一个应用程序,执行相同的操作,但使用StringBuffer而不是不可变字符串。(例如,您可以附加到StringBuffer以进行修改。)打印的StringBuffer将具有相同的值,这是因为它是(a)作为Java传递给方法作为参数的所有对象一样被引用传递和(b)可变的。

我希望这能帮助那些正在阅读这个线程并试图学习的人!


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