可能对价值传递如何运作的误解
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
的状态。在 p
和 c.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
中的Ref11325
、p
中的Ref21354
以及p
的name
字段中的Ref54312
只是为了表明它们包含引用;我们永远看不到引用的实际值。
然后当你这样做时:
c.p = p;
你有(唯一的变化是 c
的 p
,当然):
+−−−−−−−−−−−−−−−−−+
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 期间创建的连接(参见虚线)。
插图应该比任何文字解释更有帮助。希望这会有所帮助。
我不确定我是否理解 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
的状态。在 p
和 c.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
中的Ref11325
、p
中的Ref21354
以及p
的name
字段中的Ref54312
只是为了表明它们包含引用;我们永远看不到引用的实际值。
然后当你这样做时:
c.p = p;
你有(唯一的变化是 c
的 p
,当然):
+−−−−−−−−−−−−−−−−−+ 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 期间创建的连接(参见虚线)。
插图应该比任何文字解释更有帮助。希望这会有所帮助。