Java字符串变量设置 - 引用还是值?

17

以下Java代码段来自于一份AP计算机科学模拟考试。

String s1 = "ab";
String s2 = s1;
s1 = s1 + "c";
System.out.println(s1 + " " + s2);
这段代码的输出结果在BlueJ上是"abc ab"。然而,可能的答案之一是"abc abc"。答案取决于Java是否像基本类型一样(按值)设置String引用,还是像对象一样(按引用)。为了进一步说明这一点,让我们来看一个关于基本类型的示例:
int s1 = 1;
int s2 = s1; // copies value, not reference
s1 = 42;

System.out.println(s1 + " " + s2); // prints "1 42"

然而,假设我们有一个BankAccount对象来保存余额。

BankAccount b1 = new BankAccount(500); // 500 is initial balance parameter
BankAccount b2 = b1; // reference to the same object
b1.setBalance(0);
System.out.println(b1.getBalance() + " " + s2.getBalance()); // prints "0 0"

我不确定字符串的情况。它们在技术上是对象,但我的编译器似乎在将变量设置为彼此时将它们视为基本类型。

如果Java像传递基本类型一样传递String变量,那么答案就是"abc ab"。然而,如果Java像引用其他任何对象一样处理String变量,那么答案将是"abc abc"。

你认为哪个答案是正确的?


你看过String的文档吗?它是否说明它是一个类还是原始类型? - DJClayworth
你可能想阅读javadoc,因为它可以回答你的问题。 - Brian Roach
5
System.out.println(s1 + " " + s2); // prints "1 42" 不正确,它输出的是 "42 1" - whytheq
@whytheq 我的输出是“42 1”。我看到这个结果时,感到非常惊讶。在 s1 被重新赋值为 42 之前,s2 已经复制了它的值。 - Codist
9个回答

32

Java中的字符串是不可变的,所以你的重新赋值实际上会让变量指向一个新的字符串实例,而不是改变字符串的值。

String s1 = "ab";
String s2 = s1;
s1 = s1 + "c";
System.out.println(s1 + " " + s2);

在第2行代码中,s1 == s2并且s1.equals(s2)都是true。在第3行拼接后,s1引用了一个不同的实例,其不可变值为"abc",因此s1==s2和s1.equals(s2)的结果都为false。


1
它确实是这样,也确实有这个功能,但这并没有真正回答提问者更基本的问题。 - Oliver Charlesworth
5
当然可以。它含蓄地解释了他将字符串案例与银行账户进行比较的不同之处。其中一个是分配一个新实例,而另一个只是修改一个现有实例。为了清晰起见,s1=s1+"c"基本上相当于b=new StringBuilder(s1);b.append('c');s1=b.toString();。 - Robin

14

你的BankAccount和String的区别在于,String是不可变的。没有“setValue()”或“setContent()”这样的东西。相应的银行账户示例为:

BankAccount b1 = new BankAccount(500); // 500 is initial balance parameter
BankAccount b2 = b1; // reference to the same object
b1 = new BankAccount(0);
System.out.println(b1.getBalance() + " " + s2.getBalance()); // prints "0 500"

因此,如果你这样考虑(实际上并非编译器的做法,但在功能上是等效的),字符串拼接情况如下:

String s1 = "ab";
String s2 = s1;
s1 = new String("abc");
System.out.println(s1 + " " + s2); //prints "abc ab"

10

无论String被视为原始类型还是对象都没有关系!

在String示例中,两个字符串的连接会产生一个新的String实例,然后将其分配给s1。变量s2仍然引用未更改的旧String实例。

假设BankAccount有一个设置余额并返回新BankAccount的方法,你的示例可能如下所示:

BankAccount b1 = new BankAccount(500); // 500 is initial balance parameter
BankAccount b2 = b1; // reference to the same object
b1 = b1.createNewAccountWithBalance(0); // create and reference a new object
System.out.println(b1.getBalance() + " " + b2.getBalance()); // prints "0 500"

9
确实,String是一个类,并且它是通过引用分配/传递的。 但令人困惑的是这个语句:
String s = "abc";

这表明String是一个原始类型(就像“int x = 10;”); 但这只是一种快捷方式,语句“String s = "abc";”实际上编译为“String s = new String("abc");” 就像“Integer x = 10;”编译为“Integer x = new Integer(10);

这种机制称为“装箱”(boxing)。

更令人困惑的是:有一个类“Integer”和一个原始类型“int”, 但String没有一个原始类型等价物(尽管char[]接近)

Sije de Haan


7
在Java中,String对象是通过引用分配和传递的;在这方面,它们的行为与任何其他对象完全相同。
然而,String是不可变的:没有一种操作可以直接修改现有字符串的值,而不创建一个新对象。例如,这意味着s1 = s1 + "c"会创建一个新对象,并用指向该新对象的引用替换存储在s1中的引用。

5

4

java.lang.String是一个对象,而不是原始类型。

第一个示例中的代码所做的是:

  1. 将s1定义为“ab”
  2. 将s2设置为与s1相同的基础对象
  3. 将s1设置为一个新字符串,该字符串是s1旧值和“c”的组合

但是要回答您有关引用或值的问题,它是按引用传递的。


1

这个断言是不正确的。Java将String变量视为对任何其他对象的引用。Strings是对象,但答案仍然是"abc ab"。

问题不在于赋值运算符的操作。在您的示例中,无论如何赋值运算符都会分配一个对String对象的引用。

问题在于连接运算符('+')的操作。它创建一个新的String对象。正如其他人所说,这是必要的,因为String对象是不可变的,但这是运算符行为的问题,而不仅仅是因为String是不可变的。即使String对象是可变的,连接运算符也可以返回一个新的对象。

相比之下,在您的第二个示例中,b1.setBalance(0)不会创建新对象,而是修改现有对象。


0
int s1 = 1;
int s2 = s1; // copies value, not reference
s1 = 42;

System.out.println(s1 + " " + s2); // prints "1 42"

输出的不是"1 42"而是"42 1"。需要考虑每一行代码的执行情况。首先s1被赋值为1,然后s2被赋值为s1,也就是1(假设此时Java还没有看到第三行代码)。接着Java看到了第三行代码并立即将s1更改为42。之后Java被告知打印它目前所知道的内容,即s1为42,s2为1(旧的s1)。

对于字符串也是同样的情况。

String s1 = "ab";
String s2 = s1;
s1 = s1 + "c";
System.out.println(s1 + " " + s2);// prints "abc ab".

Fort String,它并不一定会改变 s1,而是 s1 现在指向堆内存中的一个新的 String 对象,但旧的 "ab" 对象仍然存在,并具有 s2 的新引用!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接