Java中字符串的不变性

Lig*_*dle 211 java string immutability

请考虑以下示例.

String str = new String();

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

str = "Help!";
System.out.println(str);  //Prints Help!
Run Code Online (Sandbox Code Playgroud)

现在,在Java中,String对象是不可变的.那么为什么对象str可以赋值"帮助!".这与Java中字符串的不变性相矛盾吗?任何人都可以向我解释一下不变性的确切概念吗?

编辑:

好.我现在得到它,但只是一个后续问题.以下代码如何:

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

str = str.replace("i", "!"); 
System.out.println(str); // prints M!ss!ss!pp! 
Run Code Online (Sandbox Code Playgroud)

这是否意味着再次创建了两个对象("Mississippi"和"M!ss!ss!pp!"),并且引用str指向replace()方法之后的另一个对象?

gus*_*afc 298

str它不是一个对象,它是对象的引用."Hello"并且"Help!"是两个不同的String对象.因此,str 指向一个字符串.您可以更改它指向的内容,但不能更改它指向的内容.

拿这个代码,例如:

String s1 = "Hello";
String s2 = s1;
// s1 and s2 now point at the same string - "Hello"
Run Code Online (Sandbox Code Playgroud)

现在,没有什么1,我们可以做些什么来s1会影响价值s2.它们引用相同的对象 - 字符串"Hello"- 但该对象是不可变的,因此无法更改.

如果我们这样做:

s1 = "Help!";
System.out.println(s2); // still prints "Hello"
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到了变异对象和更改引用之间的区别.s2仍然指向我们最初设置s1指向的同一个对象.设置s1"Help!"仅更改引用,而String它最初引用的对象保持不变.

如果字符串可变的,我们可以这样做:

String s1 = "Hello";
String s2 = s1;
s1.setCharAt(1, 'a'); // Fictional method that sets character at a given pos in string
System.out.println(s2); // Prints "Hallo"
Run Code Online (Sandbox Code Playgroud)

编辑以响应OP的编辑:

如果你看一下String.replace(char,char)源代码(也可以在你的JDK安装目录中的src.zip中找到 - 专业提示就是在你想知道某些东西是如何工作的时候看到那里)你可以看到它确实如下:

  • 如果oldChar当前字符串中出现一个或多个匹配项,请复制当前字符串,其中所有匹配项oldChar都将替换为newChar.
  • 如果oldChar当前字符串中不存在,则返回当前字符串.

所以,是的,"Mississippi".replace('i', '!')创建一个新String对象.以下是:

String s1 = "Mississippi";
String s2 = s1;
s1 = s1.replace('i', '!');
System.out.println(s1); // Prints "M!ss!ss!pp!"
System.out.println(s2); // Prints "Mississippi"
System.out.println(s1 == s2); // Prints "false" as s1 and s2 are two different objects
Run Code Online (Sandbox Code Playgroud)

您现在的功课就是看上面的代码做什么,如果你改变s1 = s1.replace('i', '!');s1 = s1.replace('Q', '!');:)


1实际上,它可能的突变的字符串(和其他不可变的对象).它需要反射并且非常非常危险,除非您真正对破坏程序感兴趣,否则永远不应该使用它.

  • 我以前从未见过这样的答案.讨论每一个细节. (14认同)
  • 对于虚构方法+1,它演示了不可变对象与其他对象之间的差异. (11认同)

coo*_*ird 23

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

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

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

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

final String STR = "Hello";
Run Code Online (Sandbox Code Playgroud)

现在,尝试另一种分配StringSTR会引起编译错误.

  • 您是否曾经尝试过用 C 编程?只需阅读有关指针的入门读物,您就会完美地理解 coobird 的答案。 (2认同)

Mic*_*mlk 10

Light_handle我建议你读一下杯子大小 - 一个关于变量的故事传递价值请(杯子大小继续).阅读上述帖子时,这将有很大帮助.

你读过它们吗?是.好.

String str = new String();
Run Code Online (Sandbox Code Playgroud)

这将创建一个名为" str" 的新"遥控器",并将其设置为值new String()(或"").

例如在内存中这会创建:

str --- > ""
Run Code Online (Sandbox Code Playgroud)
str  = "Hello";
Run Code Online (Sandbox Code Playgroud)

然后,这会更改遥控器" str",但不会修改原始字符串"".

例如在内存中这会创建:

str -+   ""
     +-> "Hello"
Run Code Online (Sandbox Code Playgroud)
str = "Help!";
Run Code Online (Sandbox Code Playgroud)

然后,这会更改遥控器" str",但不会修改原始字符串""或遥控器当前指向的对象.

例如在内存中这会创建:

str -+   ""
     |   "Hello"
     +-> "Help!"
Run Code Online (Sandbox Code Playgroud)


Sha*_*are 8

让我们把它分成几个部分

String s1 = "hello";
Run Code Online (Sandbox Code Playgroud)

此Statement 在内存中创建包含hello和占用空间的字符串,即在Constant String Pool中,并将其分配给引用对象s1

String s2 = s1;
Run Code Online (Sandbox Code Playgroud)

此语句将相同的字符串hello分配给新引用s2

         __________
        |          |
s1 ---->|  hello   |<----- s2
        |__________| 
Run Code Online (Sandbox Code Playgroud)

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

out.println(s1);    // o/p: hello
out.println(s2);    // o/p: hello
Run Code Online (Sandbox Code Playgroud)

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

s1 = "stack";    
         __________
        |          |
s1 ---->|  stack   |
        |__________|
Run Code Online (Sandbox Code Playgroud)

但是那个指向你好的s2对象会是什么样子呢.

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

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello
Run Code Online (Sandbox Code Playgroud)

由于String是不可变的,因此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
Run Code Online (Sandbox Code Playgroud)

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

out.println(s1);    // o/p: stack overflow
Run Code Online (Sandbox Code Playgroud)

现在你可能会惊讶为什么String有像concat()这样的方法来修改.以下片段将清除您的困惑.

s1 = s1.concat(" overflow");
Run Code Online (Sandbox Code Playgroud)

这里我们将字符串的修改值赋给s1引用.

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


out.println(s1);    // o/p: stack overflow
out.println(s2);    // o/p: hello
Run Code Online (Sandbox Code Playgroud)

这就是为什么Java决定将String作为最终类的原因,否则任何人都可以修改和更改string的值.希望这会有所帮助.


dav*_*veb 6

首次引用的字符串对象str未被更改,您所做的只是str引用一个新的字符串对象.


akf*_*akf 5

字符串不会改变,对它的引用会.你将不可变性与final字段的概念混为一谈.如果某个字段被声明为final,则一旦分配了该字段,就无法重新分配该字段.


use*_*421 5

关于问题的替换部分,请尝试以下方法:

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

String other = str.replace("i", "!"); 
System.out.println(str); //still prints Mississippi 
System.out.println(other);  // prints M!ss!ss!pp!
Run Code Online (Sandbox Code Playgroud)