考虑下面的例子。

String str = new String();

str  = "Hello";
System.out.println(str);  //Prints Hello

str = "Help!";
System.out.println(str);  //Prints Help!

在Java中,String对象是不可变的。那么为什么对象str可以被赋值为"Help!"呢?这难道不是与Java中字符串的不变性相矛盾吗?有人能给我解释一下不变性的确切概念吗?

编辑:

好的。我现在明白了,但还有一个问题。下面的代码呢:

String str = "Mississippi"; 
System.out.println(str); // prints Mississippi 

str = str.replace("i", "!"); 
System.out.println(str); // prints M!ss!ss!pp! 

这是否意味着将再次创建两个对象(“Mississippi”和“M!ss!ss!pp!”),并且在replace()方法之后引用str指向不同的对象?


当前回答

字符串是不可变的。这意味着我们只能改变引用。


String a = "a";
System.out.println("String a is referencing to "+a); // Output: a

a.concat("b");
System.out.println("String a is referencing to "+a); // Output: a

a = a.concat("b");
System.out.println("String a has created a new reference and is now referencing to "+a); // Output: ab

其他回答

字符串不会改变,但对它的引用会改变。你混淆了不可变性和final字段的概念。如果一个字段被声明为final,那么一旦它被赋值,就不能重新赋值。

str引用的对象可以更改,但实际的String对象本身不能更改。

包含字符串“Hello”和“Help!”的String对象不能改变它们的值,因此它们是不可变的。

String对象的不可变性并不意味着指向该对象的引用不能改变。

防止str引用更改的一种方法是将其声明为final:

final String STR = "Hello";

现在,尝试将另一个String赋值给STR将导致编译错误。

在Java中,对象通常是通过引用访问的。在你的代码段中,str是一个引用,它首先被赋值给“Hello”(一个自动创建的对象或从常量池中获取的对象),然后你将另一个对象“Help!”赋值给同一个引用。需要注意的一点是,引用是相同的,但对象是不同的。代码中还有一件事,你访问了三个对象,

当你调用new String()。 当你分配"hello"的时候。 当你说“救命!”

调用new String()会创建一个新对象,即使它存在于字符串池中,所以通常不应该使用它。要将new string()创建的字符串放入字符串池,可以尝试intern()方法。

我希望这能有所帮助。

就像莱纳斯·托瓦兹说的:

空谈是廉价的。给我看看代码

看看这个:

public class Test{
    public static void main(String[] args){

        String a = "Mississippi";
        String b = "Mississippi";//String immutable property (same chars sequence), then same object

        String c = a.replace('i','I').replace('I','i');//This method creates a new String, then new object
        String d = b.replace('i','I').replace('I','i');//At this moment we have 3 String objects, a/b, c and d

        String e = a.replace('i','i');//If the arguments are the same, the object is not affected, then returns same object

        System.out.println( "a==b? " + (a==b) ); // Prints true, they are pointing to the same String object

        System.out.println( "a: " + a );
        System.out.println( "b: " + b );

        System.out.println( "c==d? " + (c==d) ); // Prints false, a new object was created on each one

        System.out.println( "c: " + c ); // Even the sequence of chars are the same, the object is different
        System.out.println( "d: " + d );

        System.out.println( "a==e? " + (a==e) ); // Same object, immutable property
    }
}

输出为

a==b? true
a: Mississippi
b: Mississippi
c==d? false
c: Mississippi
d: Mississippi
a==e? true

所以,记住两件事:

字符串是不可变的,直到你应用一个方法来操作和创建一个新的字符串(c和d的情况)。 如果两个参数相同,则Replace方法返回相同的String对象

让我们把它分成几个部分

String s1 = "hello";

此语句创建包含hello的字符串并占用内存空间,即在常量字符串池中,并将其分配给引用对象s1

String s2 = s1;

这条语句将相同的字符串hello赋值给新引用s2

         __________
        |          |
s1 ---->|  hello   |<----- s2
        |__________| 

两个引用都指向相同的字符串,因此输出相同的值,如下所示。

out.println(s1);    // o/p: hello
out.println(s2);    // o/p: hello

虽然String是不可变的,但可以赋值,因此s1现在将引用新的值堆栈。

s1 = "stack";    
         __________
        |          |
s1 ---->|  stack   |
        |__________|

但是指向hello的s2对象呢它还是原来的样子。

         __________
        |          |
s2 ---->|  hello   |
        |__________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello

由于字符串是不可变的,Java虚拟机不允许我们通过它的方法修改字符串s1。它将在池中创建所有新的String对象,如下所示。

s1.concat(" overflow");

                 ___________________
                |                   |
s1.concat ----> |  stack overflow   |
                |___________________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello
out.println(s1.concat); // o/p: stack overflow

注意,如果String将是可变的,那么输出将是

out.println(s1);    // o/p: stack overflow

现在,您可能会感到惊讶,为什么String需要修改concat()这样的方法。下面的代码片段将消除您的困惑。

s1 = s1.concat(" overflow");

这里我们将修改后的string值赋值给s1引用。

         ___________________
        |                   |
s1 ---->|  stack overflow   |
        |___________________|


out.println(s1);    // o/p: stack overflow
out.println(s2);    // o/p: hello

这就是为什么Java决定将String作为最终类,否则任何人都可以修改和改变String的值。 希望这能有所帮助。