我在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编译器的实现是否有问题?
答案 0 :(得分:20)
您上面发布的代码实际上并没有改变任何字符串,尽管它看起来像。原因是这一行不会改变字符串:
string = string + "bcd";
相反,它的作用是:
string + "bcd"
的新字符串。string
引用的字符串以引用此新字符串。换句话说,实际的具体字符串对象本身并未更改,但确实修改了引用到这些字符串。 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)那么它就是可变的。