可能误解了按值传递的工作原理

Possible missunderstanding of how pass by value works

提问人:Chris 提问时间:9/17/2017 更新时间:9/18/2017 访问量:45

问:

我不确定我是否了解 Java 中的按值传递的工作原理。我知道有很多关于这个的话题,即使在 SO 上也是如此,但它们都不太合适,我想知道我是否正确:

假设我有以下代码:

class Class{
    public String className;
    public Person p;
}

class Person {
    public String name;
}

我不是 100% 理解的是:

Class c = new Class(); // reference of the object oC is stored in var c
Person p = new Person();// reference of the object oP is stored in var p
p.name = "StudName"; // value = reference of string s is stored in var p
c.p = p; // reference of p is updated to point to oP
p = new Person(); // Confusing Part (1)

所以据我了解,每个变量在堆栈中都有一个给定的空间,并指向对象的引用,对吗?那么我是否正确,不会影响 Object 中的引用,因为此操作将 的引用设置为新创建的 Person 对象中的引用?p = new Person();cp

我觉得这很难理解,因为如果我做这样的事情:

c.p = new Person();//(2)

这当然会影响参考。但是(1)和(2)有什么区别呢?c.p

Java 引用 值传递

评论

0赞 Jacek Cz 9/17/2017
可能重复 stackoverflow.com/questions/40480/......
0赞 Chris 9/17/2017
@JacekCz 不,这并不是真正的重复,因为没有解释重新同化是如何工作的
0赞 peterulb 9/17/2017
实际上,“value = reference of string s is stored in var p”是错误的。它不是存储在 p 中,而是存储在“name”字段中。在(1)中,你说变量p将是对一个新人物的引用。在 (2) 中,您说在 c 中作为变量存储的引用 p 将指向一个新的 Person。在 (2) 中,c 仍将指向原始 Class 实例。

答:

3赞 T.J. Crowder 9/17/2017 #1

您的问题与按值传递*无关。您的问题是关于对象引用的。

每个变量在堆栈中都有一个给定的空间,并指向对象的引用,对吗?

你有太多的间接层次。:-)该变量包含对象引用。

将对象引用视为告诉 JVM 对象在内存中其他位置的位置。(这并不是字面上的正确,但这是一种非常好的思考方式。int

所以在:

c.p = p;

中的值(表示该对象所在位置的对象引用)被复制到 ,修改对象的状态。和 之间没有持续的关系,它们只是恰好暂时包含相同的值。pPersonc.pcpc.p

这就像我们在课堂上有一个,并且做了:int i

int i = 42;
c.i = i;

将 中的复制到 中,修改 的状态。ic.ic

在行之后,当您执行此操作时:c.p = ...

p = new Person(); // Confusing Part (1)

...它根本没有影响。它只是创建一个新对象并将对象引用存储在 中。 仍然具有旧值(对早期对象的引用)。c.pPersonpc.p

但是(1)和(2)有什么区别呢?

在 (1) 中,您分配的是 ,而不是 ;这样做对 没有影响。在 (2) 中,您要分配给 .pc.pc.pc.p

让我们按照第一个代码块进行操作,但我将使用 Java,而不是因为 Java 已经有一个类。执行此操作后:ContainerClassClass

Container c = new Container();
Person p = new Person();
p.name = "StudName";

你的内存中有这样的东西:

                 +−−−−−−−−−−−−−−−−−+
c: [Ref11325]−−−>|   (Container)   |
                 +−−−−−−−−−−−−−−−−−+
                 | className: null |
                 | p: null         |
                 +−−−−−−−−−−−−−−−−−+

                                         +−−−−−−−−−−−−−−−−−−+
p: [Ref21354]−−−−−−−−−−−−−−−−−−−−−−−−−−−>|    (Person)      |
                                         +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                         | name: [Ref54312] |−−−>|  (String)  |
                                         +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                                                 | "studName" |
                                                                 +−−−−−−−−−−−−+

(省略了许多细节;例如,字符串实际上引用了其他地方的 char[] 数组,并且还有其他几个字段。

in 、 in 和 in 的字段只是为了表明它们包含引用;我们永远不会看到引用的实际值。Ref11325cRef21354pRef54312pname

然后,当您这样做时:

c.p = p;

你有(当然,只有变化是):cp

                 +−−−−−−−−−−−−−−−−−+
c: [Ref11325]−−−>|   (Container)   |
                 +−−−−−−−−−−−−−−−−−+
                 | className: null |
                 | p: [Ref21354]   |−−+
                 +−−−−−−−−−−−−−−−−−+  |
                                      |
                                      \    +−−−−−−−−−−−−−−−−−−+
p: [Ref21354]−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>|    (Person)      |
                                           +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                           | name: [Ref54312] |−−−>|  (String)  |
                                           +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                                                   | "studName" |
                                                                   +−−−−−−−−−−−−+

查看如何从 复制到 。Ref21354pc.p

最后,当您这样做时:

p = new Person();

你有这个:

                 +−−−−−−−−−−−−−−−−−+
c: [Ref11325]−−−>|   (Container)   |
                 +−−−−−−−−−−−−−−−−−+
                 | className: null |       +−−−−−−−−−−−−−−−−−−+                       
                 | p: [Ref21354]   |−−−−−−>|    (Person)      |                       
                 +−−−−−−−−−−−−−−−−−+       +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                           | name: [Ref54312] |−−−>|  (String)  |
                                           +−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−−−+
                                                                   | "studName" |
                                                                   +−−−−−−−−−−−−+

                                           +−−−−−−−−−−−−−−−−−+
p: [Ref34851]−−−−−−−−−−−−−−−−−−−−−−−−−−−−−>|    (Person)     |
                                           +−−−−−−−−−−−−−−−−−+
                                           | name: null      |
                                           +−−−−−−−−−−−−−−−−−+

p现在包含一个新的引用,但这对 完全没有影响。c.p


* “Pass-by-value”和“by-reference-by-reference”是艺术术语,与将变量传递到函数中时发生的情况有关:

doSomething(someVariable);

在 passby-value 中,将 的传递到 。在传递引用中,对变量的引用被传递到 中。在传递引用中,函数可以伸出并更改变量的内容。在按值传递中,它不能。someVariabledoSomethingsomeVariabledoSomething

“pass-by-reference”中的“reference”一词与对象引用中的“reference”一词无关,它只是使用同一个重载词定义的两件事。传递引用中的“引用”是对变量的引用,而不是对象。

评论

1赞 Chris 9/18/2017
很棒的答案。非常感谢您的详细回答
0赞 David 9/17/2017 #2

执行此操作时:

Person p = new Person();

的实例是在堆上创建的(第一个实例),只要有东西引用它,它就存在。 是堆栈上的变量,其值是对该实例的引用。Personp

执行此操作时:

p = new Person();

再次在堆上创建一个实例(第二个实例)。为局部变量分配对该实例的引用的新值。上一个实例(第一个实例)不再对它有任何引用,并且正在进行垃圾回收。Personp

执行此操作时:

c.p = new Person();

再次在堆上创建一个实例(第三个实例)。当然,局部变量不受影响,因此“第二实例”也不受影响。局部变量还指向堆上的实例,该实例仍然是它的第一个实例。该实例包括一个名为 的类级变量,该变量现在遵循与上面第二个示例相同的规则。它以前指向堆上的实例,其值更新为指向新实例(第三个实例)。PersonpcClasspPerson

这些变量中的“值”只是对象所在的堆中的一种地址。

1赞 Mikita Berazouski 9/17/2017 #3

在我准备OCA和OCP认证的过程中,我发现了一个非常好的建议--在纸上画出每个关系。

在您的案例中,对象、值和引用之间的关系将如下。enter image description here

在操作 #5 中,您将删除在操作 #2 期间创建的连接(请参阅虚线)。

插图应该比任何文字解释更有帮助。希望这会有所帮助。

评论

0赞 T.J. Crowder 9/17/2017
比我的 ASCII 艺术漂亮多了。:-)
1赞 Mikita Berazouski 9/18/2017
@T.J.Crowder谢谢。对不起,我因为某种原因错过了你的帖子......也许是因为参与了绘画。=)