考虑下面的例子。
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 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的值。
希望这能有所帮助。
Java中的String在Immutable和Final中只是意味着它不能被更改或修改:
案例1:
class TestClass{
public static void main(String args[]){
String str = "ABC";
str.concat("DEF");
System.out.println(str);
}
}
输出:美国广播公司(ABC)
原因:对象引用str没有改变,实际上是一个新对象
在池中创建了“DEF”,它根本没有引用
(也就是失去了)。
案例2:
class TestClass{
public static void main(String args[]){
String str="ABC";
str=str.concat("DEF");
System.out.println(str);
}
}
输出:六边形ABCDEF
原因:在这种情况下,str现在引用一个新对象“ABCDEF”
因此它打印ABCDEF,即前一个str对象“ABC”在池中丢失,没有引用。
STR不是一个对象,而是一个对象的引用。“Hello”和“Help!”是两个不同的String对象。因此,str指向一个字符串。你可以改变它指向的东西,但不能改变它指向的东西。
以下面的代码为例:
String s1 = "Hello";
String s2 = s1;
// s1 and s2 now point at the same string - "Hello"
现在,我们对s1做什么都不会影响s2的值。它们引用同一个对象——字符串“Hello”——但该对象是不可变的,因此不能被修改。
如果我们这样做:
s1 = "Help!";
System.out.println(s2); // still prints "Hello"
这里我们看到了改变对象和改变引用之间的区别。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"
编辑以回应OP的编辑:
如果你看一下String.replace(char,char)的源代码(也可以在JDK安装目录的src.zip中找到——一个专业的提示是,当你想知道某些东西是如何工作的时候,可以查看那里),你可以看到它所做的如下所示:
如果当前字符串中出现了一次或多次oldChar,则复制当前字符串,其中所有出现的oldChar都被newChar替换。
如果oldChar在当前字符串中不存在,则返回当前字符串。
所以,是的,“密西西比”。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
你现在的作业是看看如果你改变s1 = s1,上面的代码会做什么。替换(“我”、“!”);到s1 = s1。替换(“问”、“!”);:)
1实际上,改变字符串(和其他不可变对象)是可能的。它需要反思,而且非常非常危险,永远不应该使用,除非你真的有兴趣破坏程序。