[英]String object is immutable but reference variable is mutable. What does that mean?
I was studying Kathy Sierra Java book.我正在学习 Kathy Sierra Java 的书。 I came across one question something like this:我遇到了一个这样的问题:
public class A {
public static void main(String args[]){
String s1 = "a";
String s2 = s1;
//s1=s1+"d";
System.out.println(s1==s2);
}
}
output: true
输出: true
Two points I didn't understand here are:我在这里不明白的两点是:
s1 = s1 + "d"
output changes to false
.当我取消注释s1 = s1 + "d"
输出更改为false
。 Same thing happens if I replace String with wrapper Integer
or int
.如果我用包装器Integer
或int
替换 String ,也会发生同样的事情。 Again, when I change my code to use StringBuffer
like this:同样,当我更改代码以使用StringBuffer
:
StringBuffer sb = new StringBuffer("a"); StringBuffer sb2 = sb; //sb.append("c"); System.out.println(sb == sb2);
now the output doesn't changes ie it remains true
even if I uncomment the sb.append
statement.现在输出不会改变,即即使我取消对sb.append
语句的注释,它仍然是true
。
I can't understand this strange behavior.我无法理解这种奇怪的行为。 Can some one explain me.有人能解释一下吗。
s2
is a reference to s1
in the first case.在第一种情况下, s2
是对s1
的引用。 In the second case, +
is translated to s1.concat("d")
which creates a new string, so the references s1
and s2
point to different string objects.在第二种情况下, +
被转换为s1.concat("d")
,它创建一个新字符串,因此引用s1
和s2
指向不同的字符串对象。
In the case of StringBuffer
, the reference never changes.在StringBuffer
的情况下,引用永远不会改变。 append
changes the internal structure of the buffer, not the reference to it. append
更改缓冲区的内部结构,而不是对其的引用。
Immutable scenario不可变场景
The String
class and wrapper classes like Integer
and Double
are all immutable . String
类和包装类(如Integer
和Double
都是不可变的。 This means that when you do something like:这意味着当您执行以下操作时:
1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false
Notice what really happens under the covers ( very simplified, and using bogus memory addresses):注意幕后真正发生的事情(非常简化,并使用伪造的内存地址):
"a"
at memory address 0x000001
. (第 1 行)在内存地址0x000001
处创建一个字符串"a"
。s1
to 0x000001
so that it effectively points to string "a"
. (第 1 行)将s1
的值设置为0x000001
,使其有效地指向字符串"a"
。s1
and set it to s2
. (第 2 行)复制s1
的值并将其设置为s2
。 So now both s1
and s2
have the same value of 0x000001
, and so both point to string "a"
.所以现在s1
和s2
都有相同的值0x000001
,所以都指向字符串"a"
。s1
is pointing to (string "a"
), and use that to create a new and distinct string of "ab"
that will live at a different memory address of 0x000002
. (第 3 行)找到s1
指向的内容(字符串"a"
),并使用它来创建一个新的不同的"ab"
字符串,该字符串将位于不同的内存地址0x000002
。 (Note that string "a"
remains unchanged at memory address 0x000001
). (请注意,字符串"a"
在内存地址0x000001
处保持不变)。0x000002
to variable s1
so that it now effectively points to this new string "ab"
. (第 3 行)现在将值0x000002
分配给变量s1
以便它现在有效地指向这个新字符串"ab"
。s1
and s2
, which are now at 0x000002
and 0x000001
respectively. (第 4 行)比较s1
和s2
的值,它们现在分别位于0x000002
和0x000001
。 Clearly, they don't have the same values (memory addresses) so the result is false
.显然,它们没有相同的值(内存地址),因此结果为false
。false
to the console. (第 4 行)向控制台打印false
。 So you see, when changing the "a"
string to a "ab"
string, you were not modifying the "a"
string.所以你看,当将"a"
字符串更改为"ab"
字符串时,您并没有修改"a"
字符串。 Rather, you were creating a 2nd distinct string with the new value of "ab"
, and then changing a reference variable to point to this newly created string.相反,您使用"ab"
的新值创建了第二个不同的字符串,然后更改了一个引用变量以指向这个新创建的字符串。
The exact same pattern occurs when coding with the other classes like Integer
or Double
, which are immutable as well.使用其他类(如Integer
或Double
编码时会出现完全相同的模式,这些类也是不可变的。 You have to understand that when you use operators like +
or -
on instances of these classes, you are not modifying the instance in any way.您必须了解,当您在这些类的实例上使用+
或-
等运算符时,您并没有以任何方式修改实例。 Rather, you are creating a whole new object, and getting a new reference to that new object's memory address that you can then assign to a reference variable.相反,您正在创建一个全新的对象,并获得对该新对象的内存地址的新引用,然后您可以将其分配给引用变量。
Mutable scenario可变场景
This is in complete contrast to mutable classes like StringBuffer
or StringBuilder
, and others like the unfortunate java.util.Date
.这与StringBuffer
或StringBuilder
等可变类以及不幸的java.util.Date
等其他类完全相反。 (BTW, it's best you get in the habit of using StringBuilder
instead of StringBuffer
, unless you are using it on purpose for a multi-threaded requirement) (顺便说一句,你最好养成使用StringBuilder
而不是StringBuffer
的习惯,除非你是为了多线程需求而故意使用它)
With mutable classes, the exposed methods of these classes do change (or mutate) the internal state of the object, instead of creating a whole new object.对于可变类,这些类的公开方法确实会改变(或变异)对象的内部状态,而不是创建一个全新的对象。 As a result, if you have multiple variables pointing to the same mutable object, if one of those variables is used to access the object and make changes to it, accessing that same object from any of the other variables will see the changes as well.因此,如果您有多个变量指向同一个可变对象,如果其中一个变量用于访问该对象并对其进行更改,则从任何其他变量访问同一个对象也会看到更改。
So if we take this code, for instance (again, please use StringBuilder
instead, the net result will be the same):因此,如果我们采用此代码,例如(同样,请改用StringBuilder
,最终结果将相同):
1. StringBuffer sb = new StringBuffer("a");
2. StringBuffer sb2 = sb;
3. sb.append("b");
4. System.out.println(sb == sb2); // prints true
Notice how different this is handled internally (again, very simplified, even omitting some details to keep it simple and understandable):请注意内部处理的不同之处(同样,非常简化,甚至省略了一些细节以使其简单易懂):
StringBuffer
instance at memory address 0x000001
with an internal state of "a"
. (第 1 行)在内存地址0x000001
处创建一个新的StringBuffer
实例,内部状态为"a"
。sb
to 0x000001
so that it effectively points to the StringBuffer
instance, which itself contains "a"
as part of its state. (第 1 行)将sb
的值设置为0x000001
以便它有效地指向StringBuffer
实例,该实例本身包含"a"
作为其状态的一部分。sb
and set it to sb2
. (第 2 行)复制sb
的值并将其设置为sb2
。 So now both sb
and sb2
have the same value of 0x000001
, and so both point to the same StringBuffer
instance.所以现在sb
和sb2
都有相同的值0x000001
,所以都指向同一个StringBuffer
实例。sb
is pointing to (the StringBuffer
instance), and call the .append()
method on it to ask it to mutate its state from "a"
to "ab"
. (第 3 行)找到sb
指向的内容( StringBuffer
实例),并对其调用.append()
方法以要求其将其状态从"a"
更改为"ab"
。 ( Very important!!! Unlike the immutable version, the memory address of sb
does NOT change. So both sb
and sb2
are still pointing to the same StringBuffer
instance. (非常重要!!!与不可变版本不同, sb
的内存地址不会改变。所以sb
和sb2
仍然指向同一个StringBuffer
实例。sb
and sb2
, which are both still at 0x000001
. (第 4 行)比较sb
和sb2
的值,它们都仍在0x000001
。 This time, they both have the same value, so the result is true
.这一次,它们都具有相同的值,因此结果为true
。true
to the console. (第 4 行)向控制台打印true
。 Bonus consideration: ==
vs. equals()
额外考虑: ==
equals()
Once you understand the above, then you now have the required knowledge to better understand this peculiar scenario:一旦你理解了上述内容,那么你现在就拥有了更好地理解这个特殊场景所需的知识:
1. String s1 = "abc";
2. String s2 = new String(s1);
3. System.out.println(s1 == s2); // prints false?!?
4. System.out.println(s1.equals(s2)); // prints true
Surprisingly, line 3 returns false
(?!?).令人惊讶的是,第 3 行返回false
(?!?)。 However, once we understand what the ==
operator is comparing, combined with a better understanding of immutable classes like String
, then it's actually not that hard to understand, and it teaches us a valuable lesson.但是,一旦我们了解==
运算符所比较的内容,再加上对String
等不可变类的更好理解,那么它实际上并不难理解,并且它教会了我们宝贵的一课。
So if we do the exercise again of examining what is really happening, we find the following:因此,如果我们再次进行练习以检查实际发生的情况,我们会发现以下内容:
"abc"
at memory address 0x000001
. (第 1 行)在内存地址0x000001
处创建字符串"abc"
。s1
to 0x000001
so that it effectively points to string "abc"
. (第 1 行)将s1
的值设置为0x000001
,使其有效地指向字符串"abc"
。"abc"
at memory address 0x000002
. (第 2 行)在内存地址0x000002
处创建一个新字符串"abc"
。 (Note that we now have 2 strings "abc"
. One at memory address 0x000001
, and the other one at 0x000002
). (请注意,我们现在有 2 个字符串"abc"
。一个在内存地址0x000001
,另一个在0x000002
)。s2
to 0x000002
so that it effectively points to the 2nd string "abc"
. (第 2 行)将s2
的值设置为0x000002
以便它有效地指向第二个字符串"abc"
。s1
and s2
, which are now at 0x000001
and 0x000002
respectively. (第 3 行)比较s1
和s2
的值,它们现在分别位于0x000001
和0x000002
。 Clearly, they don't have the same values (memory addresses) so the result is false
.显然,它们没有相同的值(内存地址),因此结果为false
。 (Even though they are both pointing to strings that are the same logically speaking, in memory, they are still 2 distinct strings!) (即使它们都指向逻辑上相同的字符串,但在内存中,它们仍然是 2 个不同的字符串!)false
to the console. (第 3 行)向控制台打印false
。.equals()
on the string pointed to by variable s1
(address 0x000001
). (第 4 行.equals()
对变量s1
(地址0x000001
)指向的字符串调用.equals()
)。 And as a parameter, pass a reference to the string pointed to by variable s2
(address 0x000002
).并作为参数传递对变量s2
(地址0x000002
)指向的字符串的引用。 The equals
method compares the values of both strings, and determines that they are logically equal, so it returns true
. equals
方法比较两个字符串的值,并确定它们在逻辑上相等,因此返回true
。true
to the console. (第 4 行)向控制台打印true
。Hopefully, the above now makes sense to you.希望以上内容现在对您有意义。
And the lesson?那课呢?
==
is not the same as equals()
. ==
与equals()
。
==
will blindly check to see if the variables' values are the same. ==
会盲目地检查变量的值是否相同。 In the case of reference variables, the values are memory address locations.在引用变量的情况下,值是内存地址位置。 So, even if 2 variables point to logically equivalent objects, if they are different objects in memory, it will return false.因此,即使 2 个变量指向逻辑上等效的对象,如果它们在内存中是不同的对象,它也会返回 false。
equals()
is meant to check for logical equality. equals()
用于检查逻辑相等性。 What that means exactly depends on the specific implementation of the equals()
method that you invoke.这意味着什么完全取决于您调用的equals()
方法的具体实现。 But in general, this is the one that returns the result we expect intuitively, and is the one you want to use when comparing strings to avoid nasty unexpected surprises.但总的来说,这是返回我们直观期望的结果的方法,也是您在比较字符串时要使用的方法,以避免出现令人讨厌的意外。
If you need more information, I recommend you do further searches on the topic of immutable vs mutable classes.如果您需要更多信息,我建议您进一步搜索不可变类与可变类的主题。 And also on the topic of value vs. reference variables.还有关于值与参考变量的话题。
I hope this helps you.我希望这可以帮助你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.