传值(StringBuilder与String之间的区别)

13

我不明白为什么System.out.println(name)输出Sam时,并没有被该方法的concat函数所影响,而System.out.println(names)则因该方法的append函数输出Sam4。为什么StringBuilder会受到影响而String却不会呢?通常,在对象引用上调用方法会影响调用者,所以我不明白为什么String类型的结果保持不变。谢谢!

public static String speak(String name) {
    name = name.concat("4");
    return name;
}

public static StringBuilder test(StringBuilder names) {
    names = names.append("4");
    return names; 
}

public static void main(String[] args) {
    String name = "Sam";
    speak(name);
    System.out.println(name); //Sam
    StringBuilder names = new StringBuilder("Sam");
    test(names);
    System.out.println(names); //Sam4
}

这里有很多答案,基本上都在说同样的事情。难以选择哪一个点赞 :) - Arnaud Denoyelle
2
@ArnaudDenoyelle 幸运的是,你不需要只选择一个 ;) - Mark Rotteveel
@ArnaudDenoyelle 就像 Mark Rotteveel 所说的那样,Helenesh 是那个必须要做出艰难决定的人 ;) - Karthik
name和names是本地引用,语义上类似于指针被分配,只有StringBuilder.append可以在引用的对象上工作。 - Sam Ginrich
9个回答

14
因为当你调用speak(name);时,在speak内部执行
name = name.concat("4");

它创建一个新的对象,因为 String 是不可变的。当你改变原始字符串时,它会创建一个新的对象,我同意你正在返回它,但你没有捕获它。

所以本质上你正在做的是:

name(new) = name(original) + '4'; // but you should notice that both the names are different objects.

尝试

String name = "Sam";
name = speak(name);
当然,现在我认为无需解释为什么使用 StringBuilder 能够运行,除非你不知道 StringBuilder 是可变的。

它创建一个新对象,因为字符串是不可变的。在该语言中没有“可变性”的概念。它返回一个新对象,因为方法记录了这样做。它不会修改调用它的对象,同样是因为方法记录了这样做。 - newacct
1
@newacct 对不起,先生,我确信我不如您了解得多,但我认为如果我说“它返回一个新对象,因为其方法已记录为这样做”,那么OP可能不会很高兴。所以我想给出一些背后的推理。 - Karthik
但我们之所以说这个类是“不可变的”,仅仅是因为它的所有方法都被记录为不会修改它。因此,如果说这些方法不会修改它是因为它是不可变的,那就是循环论证了。 - newacct
@newacct 可能就像“鸡生蛋,蛋生鸡”一样:P - Karthik

9
查看 String类的Javadoc,可以发现:

[...] 字符串对象是不可变的 [...].

这意味着concat(String)方法并不会改变原始的字符串对象,而是构造一个新的字符串对象。
相反地,StringBuilder类是可变的。通过调用append(CharSequence)方法,可以直接修改字符串对象本身。

设计上是不可变的,反射确实可以更改字符串内容;当处理像密码这样敏感的字符串时,这就是你所做的。 - Sam Ginrich

4

因为 String 是不可变的,所以 String#concat 不会修改原始的字符串实例,它只返回一个新的 String,而原始的字符串保持不变。而 StringBuilder 是可变的,对传递的 StringBuilder 实例进行更改会反映在该实例中。


4

好的,speak方法在做什么?

首先,

name.concat("4");

创建一个新的对象,该对象等于name"4"连接在一起。

因此,这行代码为:

name = name.concat(4);

重新定义了 speak 方法中的本地变量 name(使用了local关键字)。

然后你使用 return 返回该新值的引用。

return name;

因此,传递给方法的原始变量未被修改,但是该方法返回已修改的值。

test方法中,您实际上修改了变量而不修改引用(StringBuilder类是可变的,因此可以修改此类型的变量)。

然后我们可以看到另一个问题:为什么StringBuilder.append返回值,这似乎是多余的。对于“构建器”模式的描述,答案在其中,这是实现修改方法的通常方式。请参见wikipedia on Builder pattern


3

String在Java中是不可变的。一旦你在name上调用concat方法,就会创建一个新的字符串,而当你在System.out.println(name)中使用旧的引用时,它仍然指向旧字符串。如果你想使用修改后的字符串,你应该显式地返回引用。 而StringBuilder是可变的,并且它总是返回相同的引用。


3
在你的 speak 方法中,concat 方法会返回一个新的字符串,调用它的原始对象不会改变(字符串是不可变的)。如文档所述:

如果参数字符串的长度为 0,则返回此 String 对象。否则,将返回一个表示由此 String 对象表示的字符序列和参数字符串表示的字符序列连接而成的字符串。

调用 name.concat("4") 等同于 name + "4"
在你的 test 方法中,append 方法会修改 StringBuilder 的内容。如文档所述:

StringBuilder 的主要操作是 appendinsert 方法,它们被重载以接受任何类型的数据。每个方法都会将给定的数据转换为字符串,然后将该字符串的字符附加或插入到字符串构建器中。 append 方法总是在构建器的末尾添加这些字符;insert 方法在指定点添加字符。

在你的主方法中,namenames 仍是调用方法前的相同对象,但是字符串是不可变的,所以 name 的内容未改变,而 names 的内容已经被修改。
如果你使用了两个方法的返回值,那么你将得到你期望的结果。

3
当你调用speak(name)时,它会计算新的值,但会将其丢弃。
如果你将其替换为:
name = speak(name);

结果将会是您所期望的。
使用StringBuilder时,您传递的对象是可变的: 因此,
names.append(names);

改变当前对象的状态(它还返回对同一对象的引用,这只是为了方便您编写类似于names.append(...).append(...)等代码)。因此,在StringBuilder的情况下,当您调用该方法时引用的对象实际上已经改变,因此您会看到更改。


你没有回答这个问题:为什么使用StringBuilder可以正常工作? - m0skit0
是的,正确的。请查看更新。 - James_D

2
首先,String 是 Java 中的一个不可变类。不可变类是指其实例不能被修改的类。实例中的所有信息在创建时初始化,并且这些信息不能被修改。
其次,在 Java 中,参数是按值传递的而不是按引用传递的。
在你的方法“test”中,你不需要使用 names = names.append("4"),而是只需要使用 names.append("4")
如果你查看 Java 的 文档,你会发现那里的大多数方法,包括 concat,都会生成一个新的字符串。
因此,为了使输出的字符串也是 Sam4,你需要在主方法中使用 name = speak(name)

0

字符串

字符串是不可变(一旦创建就不能更改)的对象。作为一个字符串创建的对象被存储在常量字符串池中。Java 中的每个不可变对象都是线程安全的,这意味着字符串也是线程安全的。字符串不能同时被两个线程使用。字符串一旦被分配,就不能被更改。

String demo = "hello"; // 上述对象存储在常量字符串池中,其值无法修改。

demo = "Bye"; // 新的“Bye”字符串在常量池中被创建,并由demo变量引用;“hello”字符串仍然存在于常量字符串池中,其值没有被覆盖,但我们丢失了对“hello”字符串的引用。

StringBuilder

StringBuilder 与 StringBuffer 相同,它将对象存储在堆中,可以被修改。StringBuffer 和 StringBuilder 的主要区别在于 StringBuilder 不是线程安全的。StringBuilder很快,因为它是非线程安全的。

更多细节请查看this

结论: 您无需重新分配值给StringBuilder,因为它已经是一个引用 测试方法应该是

public static void test(StringBuilder names) {
    names.append("4");
   }

但是应该说
 String name = "Sam";
   name =  speak(name);

这不是被问到的内容。 - m0skit0
好的,我更新了答案。 - Ali Helmy

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