简体   繁体   English

为什么下面的例子似乎反驳了 Java 中的字符串是不可变对象?

[英]Why does the following example seem to refute that Strings are immutable objects in Java?

I am using the OpenJDK Java compiler under Ubuntu.我在 Ubuntu 下使用 OpenJDK Java 编译器。 I wanted to convert a character array to a string and when that seemed to have ended up giving ambiguous results, I tried to write a toString method of my own.我想将字符数组转换为字符串,当这似乎最终给出了模棱两可的结果时,我尝试编写自己的toString方法。 In the process, I wrote a test program wherein (out of the fun of it) I tried to compile the following code.在此过程中,我编写了一个测试程序,其中(出于乐趣)我尝试编译以下代码。

class toString{
    public static void main(String[] args){
        string = "abc";
        string = string + "bcd";
        System.out.println(string);
    }
}

Now, I know that String objects in Java are immutable and the code should have in fact generated an error but to my surprise, it printed abcbcd to the console.现在,我知道 Java 中的String对象是不可变的,代码实际上应该产生错误,但令我惊讶的是,它在控制台上打印了abcbcd Does this mean that String objects in Java are mutable or is there something wrong with the implementation of OpenJDK compiler in this case?这是否意味着 Java 中的String对象是可变的,或者在这种情况下 OpenJDK 编译器的实现有问题?

The code that you've posted above does not actually mutate any strings, though it looks like it does.您在上面发布的代码实际上并没有改变任何字符串,尽管看起来确实如此。 The reason is that this line doesn't mutate the string:原因是这一行不会改变字符串:

string = string + "bcd";

Instead, what this does is:相反,它的作用是:

  1. Construct a new string whose value is string + "bcd" .构造一个值为string + "bcd"的新字符串。
  2. Change what string is referenced by string to refer to this new string.更改字符串引用的string以引用此新字符串。

In other words, the actual concrete string objects themselves weren't changed, but the references to those strings were indeed modified.换句话说,实际的具体字符串对象本身并没有改变,但对这些字符串的引用确实被修改了。 Immutability in Java usually means that objects cannot be modified, not the references to those objects. Java 中的不变性通常意味着无法修改对象,而不是对这些对象的引用。

An important detail that confuses a lot of new Java programmers is that the above line is often written as一个让很多新 Java 程序员感到困惑的重要细节是,上面的行经常写成

string += "bcd";

which looks even more strongly as though it's concatenating bcd onto the end of the string and thereby mutating it, even though it's equivalent to the above code and therefore doesn't cause any changes to the actual String object (again, it works by creating a new String object and changing what object the reference refers to.)它看起来更加强烈,好像它将bcd连接到字符串的末尾并因此对其进行了变异,即使它等同于上面的代码,因此不会对实际的String object 造成任何更改(同样,它通过创建一个新String object 并更改 object 引用所指的内容。)

To see that what's going on here is that you're actually changing the reference and not the string it refers to, you can try rewriting the code to make string final , which prevents you from changing what object is referenced.要查看这里发生的情况是您实际上是在更改引用而不是它所引用的字符串,您可以尝试重写代码以使string final ,这可以防止您更改 object 所引用的内容。 If you do so, you'll find that the code no longer compiles.如果这样做,您会发现代码不再编译。 For example:例如:

class toString{
    public static void main(String[] args){
        final String string = "abc";
        string = string + "bcd";    // Error: can't change string!
        System.out.println(string);
    }
}

One final note - another common cause of grief for new Java programmers when using String s is that String has methods that appear to mutate the string but in actuality do not.最后一点 - 新 Java 程序员在使用String s 时另一个常见的悲痛原因是String具有似乎可以改变字符串但实际上没有的方法。 For example, this code does not work correctly:例如,此代码无法正常工作:

String s = "HELLO, WORLD!";
s.toLowerCase(); // Legal but incorrect
System.out.println(s); // Prints HELLO, WORLD!

Here, the call to s.toLowerCase() does not actually convert the characters of the string to lower case, but instead produces a new string with the characters set to lower case.在这里,对s.toLowerCase()的调用实际上并没有将字符串的字符转换为小写,而是生成了一个将字符设置为小写的新字符串。 If you then rewrite the code as如果您将代码重写为

String s = "HELLO, WORLD!";
s = s.toLowerCase();   // Legal and correct
System.out.println(s); // Prints hello, world!

Then the code will behave properly.然后代码将正常运行。 Again, the key detail here is that the assignment to s does not change any concrete String object, but just adjusts what object s refers to.同样,这里的关键细节是对s的赋值不会改变任何具体的String object,而只是调整 object s所指的内容。

Hope this helps!希望这可以帮助!

Nope, no error - you're not changing the contents of any string object.不,没有错误 - 您没有更改任何字符串 object 的内容。

You're changing the value of a string variable which is entirely different.您正在更改完全不同的字符串变量的值。 Look at this as two operations:将其视为两个操作:

  • Creating a new string, the result of the expression string + "bcd"创建一个新字符串,表达式string + "bcd"的结果
  • Assigning the reference to the new string back to the string variable将对新字符串的引用分配回string变量

Let's separate them out explicitly:让我们明确地将它们分开:

String string = "abc";
String other = string + "bcd";

// abc - neither the value of string nor the object's contents have changed
System.out.println(string); 

// This is *just* changing the value of the string variable. It's not making
// any changes to the data within any objects.
string = other;

It's very important to distinguish between variables and objects .区分变量对象非常重要。 The value of a variable is only ever a reference or a primitive type value.变量的值只是一个引用或原始类型值。 Changing the value of a variable does not change the contents of the object to which it previously referred.更改变量的值不会更改它之前引用的 object 的内容。

The difference is between References to an object and the object itself.区别在于对 object 的引用和 object 本身的引用。

String XXX = "xxx";

Means: Make a new variable and assign the reference to an instance of object String that contains the literal string "xxx".方法:创建一个新变量并将引用分配给包含文字字符串“xxx”的 object 字符串的实例。

XXX = XXX + "yyy";

Means:方法:

Get the reference to the object we have in variable named XXX.获取对我们在名为 XXX 的变量中的 object 的引用。 Make a new object of type String that contains the string literal "yyy".创建一个包含字符串文字“yyy”的字符串类型的新 object。 Add them together executing the string + operator.将它们加在一起执行字符串 + 运算符。 This operation will create a new String object that contains the literal string "xxxyyy".此操作将创建一个包含文字字符串“xxxyyy”的新字符串 object。 After all this, we put the reference to the new object again in the variable XXX.毕竟,我们再次将新 object 的引用放在变量 XXX 中。

The old referenced object containing "xxx" is not used anymore but the content of it was never modified.旧的引用 object 包含“xxx”不再使用,但它的内容从未修改过。

As a counter proof, there is an example:作为反证,有一个例子:

String a = "abc";
String b = "def";
String c = a;

a = a + b;

System.out.println(a); // will print "abcdef".
System.out.println(b); // will print "def".
System.out.println(c); // will print "abc".

// Now we compare references, in java == operator compare references, not the content of objects.

System.out.println(a == a); // Will print true
System.out.println(a == c); // Will print false, objects are not the same!

a = c;

System.out.println(a == c); // Will print true, now a and b points on the same instance.

Object instance are something "abstract" that live in a portion of memory of your program. Object 实例是“抽象”的东西,它存在于程序的 memory 的一部分中。 A reference variable is a reference to that portion of memory.引用变量是对 memory 的那部分的引用。 You can access objects only through variables (or return values).您只能通过变量(或返回值)访问对象。

A single object can have more than one variable pointing at it.单个 object 可以有多个指向它的变量。

String is immutable means that you cannot modify the content of that area of memory used by the String object, but of course, you are free to change and exchange references to it as you prefer.字符串是不可变的意味着您不能修改字符串 object 使用的 memory 区域的内容,但是当然,您可以随意更改和交换对它的引用。

It doesn't refute it.它不反驳它。 It actually won't compile since string isn't declared as a String object.它实际上不会编译,因为字符串没有声明为字符串 object。 But, let's say you meant:但是,假设您的意思是:

class toString{
    public static void main(String[] args){
        String string = "abc";
        string = string + "bcd";
        System.out.println(string);
    }
}

See the + operator creates a new String leaving "abc" in tact.请参阅 + 运算符创建一个新字符串,保留“abc”。 The original "abc" still exists, but all you've actually done is create a new string "abcbcd" and overwrite the original reference to "abc" when doing: string = string + "bcd".原来的 "abc" 仍然存在,但你实际上所做的只是创建一个新的字符串 "abcbcd" 并在执行以下操作时覆盖对 "abc" 的原始引用:string = string + "bcd"。 If you changed that code to this you'll see what I mean:如果您将该代码更改为此,您将明白我的意思:

class toString {
    public static void main(String[] args ) {
        String originalString = "abc";
        String newString = originalString + "bcd";

        System.out.println( originalString );  // prints the original "abc";
        System.out.println( newString );       // prints the new string "abcbcd";
    }
}

String objects are immutable, you are simply re-assigning the value of string to string + "bcd" in your code example. String对象不可变的,您只需在代码示例中将string的值重新分配给string + "bcd" You are not modifying the existing String object, you are creating a new one and assigning it to the old name.没有修改现有的String object,而是创建一个新字符串并将其分配给旧名称。

string = string + "bcd" sets a new instance of String to the variable string , rather than modify that object string = string + "bcd"String的新实例设置为变量string ,而不是修改 object

what this will do is that it will make a new object, and replace the old one.这样做的目的是制造一个新的 object,并替换旧的。 if you want mutable string look at string builder.如果您想要可变字符串,请查看字符串生成器。

The String variable string itself is mutable. String变量string本身是可变的。

The String object "abc" is immutable, as is the String object "bcd" , and the result of the concatenation, "abcbcd" . String object "abc"是不可变的,字符串 object "bcd"和连接的结果"abcbcd" That last result is assigned to the variable.最后一个结果分配给变量。

No String was mutated in the execution of that code snipped.在执行被剪断的代码时,没有发生任何 String 突变。

The string is immutable... all your example does is show that you can assign a new string reference to a variable.该字符串是不可变的...您的示例所做的只是表明您可以将新的字符串引用分配给变量。

If we make the code compile, and change it slightly:如果我们编译代码,并稍微改变一下:

class toString{
    public static void main(String[] args){
        String string = "abc";
        System.out.println(string);
        string = string + "bcd";
        System.out.println(string);
    }
}

You will see "abc" and then "abcbcd" which, could lead you to think that the string has changed, but it has not.您会看到“abc”,然后是“abcbcd”,这可能会导致您认为字符串已更改,但事实并非如此。

When you do the string = /* whatever */ you are overwriting what used to be in the variable called string with a new value.当您执行 string = /* 不管 */ 时,您正在用新值覆盖名为 string 的变量中曾经存在的内容。

If String had a method, such as setCharAt(int index, char value) then it would be mutable.如果 String 有一个方法,例如 setCharAt(int index, char value) 那么它是可变的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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