为什么以下示例似乎反驳Strings是Java中的不可变对象?

时间:2011-08-14 21:40:54

标签: java string mutable

我在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编译器的实现是否有问题?

9 个答案:

答案 0 :(得分:20)

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

string = string + "bcd";

相反,它的作用是:

  1. 构造一个值为string + "bcd"的新字符串。
  2. 更改string引用的字符串以引用此新字符串。
  3. 换句话说,实际的具体字符串对象本身并未更改,但确实修改了引用到这些字符串。 Java中的不变性通常意味着无法修改对象,而不是对这些对象的引用。

    令许多新Java程序员感到困惑的一个重要细节是上面的行通常写成

    string += "bcd";
    

    看起来更强烈,好像它将bcd连接到字符串的末尾并因此改变它,即使它等同于上面的代码,因此不会导致对实际{{1对象(同样,它通过创建一个新的String对象并更改引用引用的对象来工作。)

    要看到这里发生的是您实际上是在更改引用而不是它引用的字符串,您可以尝试重写代码以生成String string,这会阻止您改变引用的对象。如果你这样做,你会发现代码不再编译。例如:

    final

    最后一点 - 使用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的方法似乎会改变字符串,但实际上并没有。例如,此代码无法正常工作:

    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引用的对象。

    希望这有帮助!

答案 1 :(得分:5)

不,没有错误 - 你没有改变任何字符串对象的内容。

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

  • 创建新字符串,即表达式string + "bcd"
  • 的结果
  • 将对新字符串的引用分配回string变量

让我们明确地将它们分开:

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;

区分变量对象非常重要 。变量的值只是引用或基本类型值。更改变量的值 not 会更改先前引用的对象的内容。

答案 2 :(得分:2)

区别在于引用对象和对象本身。

String XXX = "xxx";

表示: 创建一个新变量并将引用分配给包含文字字符串“xxx”的对象String实例。

XXX = XXX + "yyy";

意思是:

获取对名为XXX的变量中的对象的引用。 创建一个String类型的新对象,其中包含字符串文字“yyy”。 将它们一起添加到执行字符串+运算符。 此操作将创建一个包含文字字符串“xxxyyy”的新String对象。 毕竟,我们在变量XXX中再次引用了新对象。

不再使用包含“xxx”的旧引用对象,但它的内容从未被修改过。

作为反证,有一个例子:

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.

对象实例是一种“抽象”的东西,它存在于程序的一部分内存中。引用变量是对该部分内存的引用。 您只能通过变量(或返回值)访问对象。

单个对象可以有多个指向它的变量。

字符串是不可变的意味着您无法修改String对象使用的内存区域的内容,但当然,您可以根据需要随意更改和交换对它的引用。

答案 3 :(得分:1)

它没有反驳它。它实际上不会编译,因为string未声明为String对象。但是,让我们说你的意思是:

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

请参阅+运算符创建一个新的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";
    }
}

答案 4 :(得分:0)

String个对象 不可变,您只是在代码示例中将string的值重新分配给string + "bcd"。您修改现有的String对象,您正在创建一个新对象并将其分配给旧名称。

答案 5 :(得分:0)

string = string + "bcd"为变量String设置string的新实例,而不是修改该对象

答案 6 :(得分:0)

这将做的是它将创建一个新对象,并替换旧对象。 如果你想要可变字符串,请查看字符串构建器。

答案 7 :(得分:0)

String 变量 string本身是可变的。

String 对象 "abc"是不可变的,String对象"bcd"和连接的结果"abcbcd"也是不可变的。最后一个结果被分配给变量。

没有字符串在执行该代码时被突变。

答案 8 :(得分:0)

该字符串是不可变的......您的所有示例都表明您可以为变量分配新的字符串引用。

如果我们编译代码,并稍微改变一下:

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

你会看到“abc”,然后是“abcbcd”,这可能会让你认为字符串已经改变,但它没有。

当你执行string = / * whatever * /时,你用一个新值覆盖了名为string的变量。

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