简体   繁体   English

字符串对象是不可变的,但引用变量是可变的。 这意味着什么?

[英]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:我在这里不明白的两点是:

  1. when I uncomment s1 = s1 + "d" output changes to false .当我取消注释s1 = s1 + "d"输出更改为false Same thing happens if I replace String with wrapper Integer or int .如果我用包装器Integerint替换 String ,也会发生同样的事情。
  2. 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") ,它创建一个新字符串,因此引用s1s2指向不同的字符串对象。

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类和包装类(如IntegerDouble都是不可变的 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):注意幕后真正发生的事情(非常简化,并使用伪造的内存地址):

  1. (line 1) Create a string "a" at memory address 0x000001 . (第 1 行)在内存地址0x000001处创建一个字符串"a"
  2. (line 1) Set the value of s1 to 0x000001 so that it effectively points to string "a" . (第 1 行)将s1的值设置为0x000001 ,使其有效地指向字符串"a"
  3. (line 2) Copy the value of 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" .所以现在s1s2都有相同的值0x000001 ,所以都指向字符串"a"
  4. (line 3) Find what 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处保持不变)。
  5. (line 3) Now assign value 0x000002 to variable s1 so that it now effectively points to this new string "ab" . (第 3 行)现在将值0x000002分配给变量s1以便它现在有效地指向这个新字符串"ab"
  6. (line 4) Compare the values of s1 and s2 , which are now at 0x000002 and 0x000001 respectively. (第 4 行)比较s1s2的值,它们现在分别位于0x0000020x000001 Clearly, they don't have the same values (memory addresses) so the result is false .显然,它们没有相同的值(内存地址),因此结果为false
  7. (line 4) Print 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.使用其他类(如IntegerDouble编码时会出现完全相同的模式,这些类也是不可变的。 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 .这与StringBufferStringBuilder可变类以及不幸的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):请注意内部处理的不同之处(同样,非常简化,甚至省略了一些细节以使其简单易懂):

  1. (line 1) Create a new StringBuffer instance at memory address 0x000001 with an internal state of "a" . (第 1 行)在内存地址0x000001处创建一个新的StringBuffer实例,内部状态为"a"
  2. (line 1) Set the value of 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"作为其状态的一部分。
  3. (line 2) Copy the value of 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.所以现在sbsb2都有相同的值0x000001 ,所以都指向同一个StringBuffer实例。
  4. (line 3) Find what 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的内存地址不会改变。所以sbsb2仍然指向同一个StringBuffer实例。
  5. (line 4) Compare the values of sb and sb2 , which are both still at 0x000001 . (第 4 行)比较sbsb2的值,它们都仍在0x000001 This time, they both have the same value, so the result is true .这一次,它们都具有相同的值,因此结果为true
  6. (line 4) Print 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:因此,如果我们再次进行练习以检查实际发生的情况,我们会发现以下内容:

  1. (line 1) Create string "abc" at memory address 0x000001 . (第 1 行)在内存地址0x000001处创建字符串"abc"
  2. (line 1) Set the value of s1 to 0x000001 so that it effectively points to string "abc" . (第 1 行)将s1的值设置为0x000001 ,使其有效地指向字符串"abc"
  3. (line 2) Create a new string "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 )。
  4. (line 2) Set the value of s2 to 0x000002 so that it effectively points to the 2nd string "abc" . (第 2 行)将s2的值设置为0x000002以便它有效地指向第二个字符串"abc"
  5. (line 3) Compare the values of s1 and s2 , which are now at 0x000001 and 0x000002 respectively. (第 3 行)比较s1s2的值,它们现在分别位于0x0000010x000002 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 个不同的字符串!)
  6. (line 3) Print false to the console. (第 3 行)向控制台打印false
  7. (line 4) Call .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
  8. (line 4) Print 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM