Java是不是永远不会传递引用?...对吗?

55

可能是重复问题:
Java是否是“按引用传递”?

今天我发现了一个不寻常的Java方法:

private void addShortenedName(ArrayList<String> voiceSetList, String vsName)
{
     if (null == vsName)
       vsName = "";
     else
       vsName = vsName.trim();
     String shortenedVoiceSetName = vsName.substring(0, Math.min(8, vsName.length()));
     //SCR10638 - Prevent export of empty rows.
     if (shortenedVoiceSetName.length() > 0)
     {
       if (!voiceSetList.contains("#" + shortenedVoiceSetName))
         voiceSetList.add("#" + shortenedVoiceSetName);
     }
}

根据我所了解的Java变量传递行为,无论是复杂对象还是简单对象,这段代码都应该什么也不做。那么...我错过了什么吗?还是这段代码应该放在thedailywtf上?


2
嘿,写一个交换两个 int 的函数 :) 是的,切换到 C# :-p - Mehrdad Afshari
1
Mehrdad:使用Integer而不是int。 - Andy
3
整数不是不可变的吗? - user85421
2
@Carlos Heuberger:是的,但你可以用一个只有一个元素的int[]来绕过它。 - Michael Myers
3
包装(在整数或数组中)并不会创建传递引用的语义。你仍然是按值传递;只是传递了一个指向包装器或数组的指针。 - Scott Stanchfield
显示剩余5条评论
6个回答

112

正如Rytmis所说,Java按值传递引用。这意味着您可以在方法的参数上合法地调用可变方法,但是您不能重新分配它们并期望值传播。

例如:

private void goodChangeDog(Dog dog) {
    dog.setColor(Color.BLACK); // works as expected!
}
private void badChangeDog(Dog dog) {
    dog = new StBernard(); // compiles, but has no effect outside the method
}

编辑:在这种情况下,这意味着虽然voiceSetList可能会因此方法而发生更改(它可以添加一个新元素),但对vsName的更改不会在方法外部可见。为了避免混淆,我经常将我的方法参数标记为final,这样它们就无法在方法内重新分配(无论是否有意)。这将防止第二个示例中的编译问题。


5
为了避免混淆,我经常使用final标记我的方法参数,这可以防止它们在方法内被重新赋值(无论是意外还是有意)。我不相信你,为什么要更改每个函数来明确一个已经存在的语言特性?我之所以这样问是因为,如果不明显地表明赋值不会修改对象,就很难理解Java。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
1
@Longpoke:我不是唯一一个阅读我的代码的人,所以我是否理解它并不重要。而且我也没有说我改变了每一个函数——尽管如果你使用像我一样的PMD,它会在你没有标记参数为final时警告你。 - Michael Myers
1
我开始感到困扰的是,在Java中,默认情况下并不是所有的变量都是final的。@Longpoke - 习惯上越严格,出错的机会就越少。为什么你不想尽可能地防止潜在的错误呢?即使这样做会导致更加明确的代码,比如创建一个新的变量而不是重新分配参数?(个人而言,大多数情况下,我不认为更明确的代码是一种代价,因此用了引号) - Bill K
5
标记参数为final是可以的,如果你想防止把参数当做局部变量来处理。但是对我而言,仅仅为了“防止”人们认为局部参数赋值会修改外部引用而标记它为final是没有意义的;如果他们一开始就这么认为,那么他们可能应该先学习Java(或者几乎任何流行的面向对象编程语言)了。另外,我同意在Java中采取限制性措施,因为这与静态类型的哲学相符,在此我打算将我的参数标记为final :) - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
@Longpoke - 我认为真正令人困惑的地方在于,当有人阅读该方法时,期望参数的值与方法调用开始时相同...而忽略了中间的赋值操作。 - Stephen C

33

Java传递的是值传递的引用,所以您会得到引用的副本,但所引用的对象是相同的。因此,该方法确实会修改输入列表。


14
Java变量的两个原则:1)变量按值传递;2)所有变量都是引用(或基本类型)。 - DJClayworth
而Java确实是关于对象行为的! - Sam Ginrich

8
参考本身按值传递。
来自Deitel&Deitel的《Java程序设计,第4版》:(第329页)
与其他语言不同,Java不允许程序员选择是按值还是按引用传递每个参数。原始数据类型变量始终按值传递。对象不会传递给方法;相反,对象的引用传递给方法。参考本身按值传递——参考的副本被传递给方法。当方法接收到对对象的引用时,该方法可以直接操作该对象。
在大学学习Java时使用了这本书。很好的参考资料。
这里有一篇解释得很好的文章。 http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html

非常好的描述。这是我读过的关于这个主题最简洁和有用的。 - JD Gamboa
传递引用和引用传值有什么区别?这样做有什么优势,还是只是Java的设计方式? - Bitwise DEVS
如果在C++中使用传递引用,对引用的操作实际上会影响传递给它的对象。而在Java中,即使将作为参数接收到的引用分配给一个新对象,原始对象也不会改变,因为你只是接收了一个引用的副本作为参数。请查看这篇文章(我没有读完整篇文章,但关于试金石测试的部分会让您感兴趣):https://www.javadude.com/articles/passbyvalue.htm - the_new_mr

2

它可以操纵 ArrayList - 这是一个对象... 如果你在传递一个对象引用(即使是按值传递),对该对象的更改将反映到调用者中。那是问题吗?


1

我认为你感到困惑是因为vsName被修改了。但在这种情况下,它只是一个本地变量,与shortenedVoiceSetName在完全相同的级别。


-4

我不清楚代码中确切的问题是什么。Java 是按值传递,但数组是按引用传递,因为它们不传递对象,只传递指针!数组由指针组成,而不是真正的对象。这使它们非常快,但也使它们处理起来很危险。要解决这个问题,您需要克隆它们以获得副本,即使这样,它也只会克隆数组的第一维。

有关更多详细信息,请参见我的答案:在 Java 中,什么是浅拷贝?(还请查看我的其他答案)

顺便说一下,由于数组只是指针,所以它们有一些优点:您可以将它们(滥)用作同步对象!


1
Java 严格按值传递,即使是数组!http://www.javadude.com/articles/passbyvalue.htm - Nico Wawrzyniak
@Sam Grinrich 正确,但理解链接的文章很重要。 - Nico Wawrzyniak
@Nico Wawrzyniak复制+1次 :( - Sam Ginrich
好的,那么你对事实持抵触态度。Java文档由Oracle编写,这是最官方和正确的资料。如果你不知道:这些人定义了Java语言。 - Nico Wawrzyniak
Oracle是事实,Java按引用传递对象是另一个事实。“Java是按值传递”的说法是著名的无稽之谈。 - Sam Ginrich
显示剩余3条评论

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