可能对价值传递如何运作的误解

Possible missunderstanding of how pass by value works

我不确定我是否理解 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)

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

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

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

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

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

each Variable has a given space in the Stack and points to the reference of the object right?

你的间接层级太多了。 :-) 变量包含对象引用。

将对象引用视为 int,它告诉 JVM 对象在内存中的其他位置。 (这不是字面意思,但这是一种很好的思考方式。)

所以在:

c.p = p;

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

这就像我们在 class 中有一个 int i 并且做了:

int i = 42;
c.i = i;

i中的复制到c.i中,修改c.

的状态

c.p = ... 行之后,当您这样做时:

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

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

But what's the difference between (1) and (2)?

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

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

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[] 数组,并且还有其他几个字段。)

c中的Ref11325p中的Ref21354以及pname字段中的Ref54312只是为了表明它们包含引用;我们永远看不到引用的实际值。

然后当你这样做时:

c.p = p;

你有(唯一的变化是 cp,当然):

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

查看 Ref21354 如何从 p 复制到 c.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" 和 "pass-by-reference" 是专业术语,与将变量传递给函数时发生的情况有关:

doSomething(someVariable);

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

"pass-by-reference" 中的 "reference" 与对象引用中的 "reference" 无关,它只是两个使用同一个重载词定义的东西。 pass-by-reference 中的 "reference" 是对 变量 的引用,而不是对象。

当你这样做时:

Person p = new Person();

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

当你这样做时:

p = new Person();

Person 的实例再次在堆上创建(第二个实例)。局部变量 p 被赋予对该实例的引用的新值。先前的实例(第一个实例)不再对它有任何引用,并且被垃圾收集。

当你这样做时:

c.p = new Person();

Person 的实例再次在堆上创建(第三个实例)。局部变量 p 当然不受影响,所以 "second instance" 也不受影响。局部变量 c 也仍然指向堆上 Class 的一个实例,这仍然是它的第一个实例。该实例包含一个名为 p 的 class 级变量,它现在遵循与上面第二个示例相同的规则。它之前指向堆上 Person 的一个实例,它的值被更新为指向一个新实例(第三个实例)。

这些变量中的"value"只是对象所在堆中的一种地址。

在准备OCA和OCP认证的过程中,我发现了一个很好的建议——把每一个关系都画在纸上。

在您的案例中,对象、值和引用之间的关系如下所示。

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

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