我实际上认为我很清楚Java中的值传递是如何工作的,因为那是我通过的SCJP证书的一部分。直到今天,当我在工作时发现了这样的方法:
public void toCommand(Stringbuffer buf) {
buf.append("blablabla");
}
然后该方法的调用者使用了这样的函数:
StringBuffer buf = new StringBuffer();
toCommand(buf);
String str = buf.toString();
现在我认为该代码会给str值“”,但实际上它给了它来自mehod的值。这怎么可能?我认为Java中没有那样的东西?
无论哪种方式......在Java中编写这样的代码应该被视为一种不好的做法,对吧?因为我可以想象它会给它带来一些困惑。
我实际上花了一些时间来搜索这个,但我对这些消息来源的解释是,它不应该工作。我错过了什么?
http://www.yoda.arachsys.com/java/passing.html
http://javadude.com/articles/passbyvalue.htm
塞巴斯蒂安
答案 0 :(得分:11)
Java是按值传递的。对象引用的值是对象引用,而不是对象本身。因此toCommand
方法接收值的副本,该副本是对象的引用 - 与调用者引用的对象相同。
这与从两个变量引用对象时完全相同:
StringBuffer buf1;
StringBuffer buf2;
buf1 = new StringBuffer();
buf2 = buf1; // Still ONE object; there are two references to it
buf1.append("Hi there");
System.out.println(buf2.toString()); // "Hi there"
免费的ASCII艺术:
+--------------------+ buf1--------->| | | === The Object === | | | buf2--------->| Data: | | * foo = "bar" | | * x = 27 | | | +--------------------+
另一种思考方式是JVM具有所有对象的主列表,由ID索引。我们创建一个对象(buf1 = new StringBuffer();
),JVM为对象分配ID 42,并将该ID存储在buf1
中。每当我们使用buf1
时,JVM从中获取值42并在其主列表中查找对象,并使用该对象。当我们buf2 = buf1;
时,变量buf2
获得值为42的副本,因此当我们使用buf2
时,JVM会看到对象引用#42和使用相同的对象。这不是一个字面上的解释(虽然从平流层的角度来看,如果你把“JVM”看作“JVM和内存管理器和操作系统”,它不是一百万英里的距离),但有助于思考实际上是什么对象引用。 / p>
在此背景下,您可以看到toCommand
如何获取引用(42或其他),而不是实际的StringBuffer
对象数据。因此,对它的操作在主列表中查找并改变其状态(因为它保存状态信息并允许我们更改它)。调用者看到对象状态的更改,因为对象保持状态,引用只指向对象。
无论哪种方式......在Java中编写这样的代码应该被视为一种不好的做法,对吗?
完全没有,这是正常的做法。如果不这样做,就很难使用Java(或大多数其他OOP语言)。与int
和long
等基元相比,对象很大,因此移动它们的成本很高;对象引用是基元的大小,因此它们很容易传递。此外,拥有事物的副本使得系统的各个部分难以进行交互。引用共享对象非常容易。
答案 1 :(得分:4)
StringBuffer
可变。 toCommand()
方法为传递值(引用)的对象获取引用,引用允许该方法更改mutable {{1} }。
如果您正在考虑为什么我们无法使用StringBuffer
执行此操作,那是因为String
不可变,在这种情况下,它会导致创建另一个String
对象,并且没有在传递引用的对象中反映更改。
我不明白为什么它应该是不好的做法。
答案 2 :(得分:2)
这是因为在方法中,当您传递对象时,引用的副本将按值传递给对象。请考虑以下示例:
public class Test {
public static void modifyBuff(StringBuffer b) {
b.append("foo");
}
public static void tryToNullifyBuff(StringBuffer b) {
b = null; // this will not affect the original reference since
// the once passed (by value) is a copy
}
public static void main(String[] args) {
StringBuffer buff = new StringBuffer(); // buff is a reference
// to StringBuffer object
modifyBuff(buff);
System.out.println(buff); // will print "foo"
tryToNullifyBuff(buff); // this has no effect on the original reference 'buff'
System.out.println(buff); // will still print "foo" because a copy of
// reference buff is passed to tryToNullifyBuff()
// which is made to reference null
// inside the method leaving the 'buff' reference intact
}
}
这可以通过其他可变对象(例如Collection类)来完成。这是 事实上,某些设计并没有采用这种模式。
答案 3 :(得分:0)
str
的值为"blablabla"
,因为StringBuilder实例的引用会传递给toCommand()
。
此处只创建了一个StringBuffer实例 - 您将对它的引用传递给toCommand()
方法。因此,StringBuffer
方法中toCommand()
上调用的任何方法都在调用方法的StringBuffer
的同一实例上调用。
答案 4 :(得分:0)
这并非总是不好的做法。考虑组装电子邮件的任务,然后您可以执行以下操作:
StringBuilder emailBuilder = new StringBuilder();
createHeader(emailBuilder);
createBody(emailBuilder);
createFooter(emailBuilder);
sendEmail(emailBuilder.toString());
它肯定可以用于制造混淆,对于公共API,如果传递的引用的值发生更改,则应该在javadoc中添加一个或两个注释。
Java API的另一个突出示例:
Collections.sort(list);
正如其他已经解释过的那样,只是为了完成答案:StringBuffer的引用值(< - 开始使用StringBuilder!)传递给toCommand
,因此在{{1}之外和之内您访问相同StringBuffer实例的方法。
答案 5 :(得分:0)
由于您获得了对实例的引用,您可以调用它上面的所有方法,但是您无法将其分配给其他任何方法。
public void toCommand(Stringbuffer buf) {
buf.append("blablabla"); // okay
buf = new Stringbuffer(); // no "effect" outside this method
}
答案 6 :(得分:0)
将对象传递给方法时,与C ++不同,只会复制对象的引用。因此,当一个可变对象传递给该方法时,您可以更改它,并且更改将反映在调用方法中。 如果您对C / C ++编程过多,您应该知道在java中,通过引用传递(在C ++语言中)是将参数传递给方法的默认(也是唯一)方法。
答案 7 :(得分:0)
只是回应一些关于SCJP的评论(抱歉没有权限发表评论 - 如果留下答案不是正确的方法,请道歉。)
为了保护SCJP考试,将对象引用作为方法参数传递,不可变的String对象和StringBuffer / StringBuilder对象之间的差异是考试的一部分 - 请参阅此处的3.1和7.3节:
http://www.javadeveloper.co.in/scjp/scjp-exam-objectives.html
Kathy Sierra / Bert Bates考试指南(这是考试的事实官方学习指南)中涵盖了这两个主题。