改变Java中传递的参数(通过ref,value调用)

时间:2010-07-02 11:47:31

标签: java parameter-passing

我实际上认为我很清楚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

塞巴斯蒂安

8 个答案:

答案 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语言)。与intlong等基元相比,对象很大,因此移动它们的成本很高;对象引用是基元的大小,因此它们很容易传递。此外,拥有事物的副本使得系统的各个部分难以进行交互。引用共享对象非常容易。

答案 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考试指南(这是考试的事实官方学习指南)中涵盖了这两个主题。