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

为什么下面的例子似乎反驳了字符串在Java中是不可变的对象?,java,string,mutable,Java,String,Mutable,我正在Ubuntu下使用OpenJDK Java编译器。我想把一个字符数组转换成一个字符串,当结果看起来不明确时,我试着写一个自己的toString方法。在这个过程中,我编写了一个测试程序,其中(出于乐趣)我试图编译以下代码 class toString{ public static void main(String[] args){ string = "abc"; string = string + "bcd"; System.out.p

我正在Ubuntu下使用OpenJDK Java编译器。我想把一个字符数组转换成一个字符串,当结果看起来不明确时,我试着写一个自己的
toString
方法。在这个过程中,我编写了一个测试程序,其中(出于乐趣)我试图编译以下代码

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

现在,我知道Java中的
String
对象是不可变的,代码实际上应该生成了一个错误,但令我惊讶的是,它将
abcbcd
打印到了控制台。这是否意味着Java中的
String
对象是可变的,或者在这种情况下OpenJDK编译器的实现有什么问题?

没有,没有错误-您没有更改任何String对象的内容

您正在更改一个完全不同的字符串变量的值。将其视为两个操作:

  • 创建新字符串时,表达式
    string+“bcd”
  • 将对新字符串的引用分配回
    字符串
    变量
让我们明确地将它们分开:

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;

区分变量和对象是非常重要的。变量的值永远只是引用或基元类型值。更改变量的值不会更改其先前引用的对象的内容。

String
对象是不可变的,您只需在代码示例中将
String
的值重新指定给
String+“bcd”
。您不是在修改现有的
字符串
对象,而是在创建一个新的对象并将其分配给旧名称。

您上面发布的代码实际上并没有改变任何字符串,尽管看起来像是这样。原因是此行不会改变字符串:

string = string + "bcd";
相反,它的作用是:

  • 构造一个新字符串,其值为
    string+“bcd”
  • 更改
    string
    引用的字符串以引用此新字符串
  • 换句话说,实际的具体字符串对象本身没有改变,但是对这些字符串的引用确实被修改了。Java中的不变性通常意味着不能修改对象,而不是对这些对象的引用

    让许多新Java程序员感到困惑的一个重要细节是,上面这一行经常被写成

    string += "bcd";
    
    它看起来更像是将
    bcd
    连接到字符串的末尾,从而对其进行变异,即使它与上述代码等效,因此不会对实际的
    字符串
    对象造成任何更改(同样,它通过创建一个新的
    字符串
    对象并更改引用的对象来工作。)

    要了解此处发生的情况是您实际更改的是引用,而不是它引用的字符串,您可以尝试重写代码以使
    字符串
    最终
    ,这将阻止您更改引用的对象。如果这样做,您会发现代码不再编译。例如:

    class toString{
        public static void main(String[] args){
            final String string = "abc";
            string = string + "bcd";    // Error: can't change string!
            System.out.println(string);
        }
    }
    
    最后一个注意事项—新Java程序员在使用
    String
    s时感到悲伤的另一个常见原因是
    String
    的方法似乎会改变字符串,但实际上不会。例如,此代码无法正常工作:

    String s = "HELLO, WORLD!";
    s.toLowerCase(); // Legal but incorrect
    System.out.println(s); // Prints HELLO, WORLD!
    
    在这里,调用
    s.toLowerCase()
    实际上不会将字符串中的字符转换为小写,而是生成一个新字符串,其中的字符设置为小写

    String s = "HELLO, WORLD!";
    s = s.toLowerCase();   // Legal and correct
    System.out.println(s); // Prints hello, world!
    
    同样,这里的关键细节是对
    s
    的赋值不会改变任何具体的
    String
    对象,而只是调整对象
    s
    所指的内容


    希望这有帮助!

    string=string+“bcd”
    string
    的新实例设置为变量
    string
    ,而不是修改该对象这样做的目的是创建一个新对象,并替换旧对象。 如果您想要可变字符串,请查看字符串生成器。

    字符串
    变量
    string
    本身是可变的

    字符串
    对象
    “abc”
    是不可变的,字符串对象
    “bcd”
    也是不可变的,连接的结果
    “abcbcd”
    。最后一个结果被分配给变量


    在执行被截断的代码时,没有字符串发生突变。

    字符串是不可变的……您的示例所做的只是显示您可以为变量分配新的字符串引用

    如果我们对代码进行编译,并稍加修改:

    class toString{
        public static void main(String[] args){
            String string = "abc";
            System.out.println(string);
            string = string + "bcd";
            System.out.println(string);
        }
    }
    
    您将看到“abc”和“abcbcd”,这可能会导致您认为字符串已更改,但它没有更改

    当您执行字符串=/*which*/时,您正在用一个新值覆盖名为string的变量中的内容


    如果字符串有一个方法,比如setCharAt(int index,char value),那么它是可变的。

    它不会反驳它。它实际上不会编译,因为字符串没有声明为字符串对象。但是,假设你的意思是:

    class toString{
        public static void main(String[] args){
            String string = "abc";
            string = string + "bcd";
            System.out.println(string);
        }
    }
    
    请参阅+运算符创建一个新字符串,并保留“abc”。原始的“abc”仍然存在,但您实际上所做的只是创建一个新字符串“abcbcd”,并在执行时覆盖对“abc”的原始引用:String=String+“bcd”。如果您将该代码更改为此,您将看到我的意思:

    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 XXX = "xxx";
    
    指: 创建一个新变量,并将引用分配给包含文本字符串“xxx”的对象字符串实例

    指:

    获取对变量XXX中对象的引用。 创建一个字符串类型的新对象,该对象包含字符串文字“yyy”。 执行字符串+运算符将它们相加
    XXX = XXX + "yyy";
    
    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.