提问人:Chris 提问时间:9/17/2017 更新时间:9/18/2017 访问量:45
可能误解了按值传递的工作原理
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)
所以据我了解,每个变量在堆栈中都有一个给定的空间,并指向对象的引用,对吗?那么我是否正确,不会影响 Object 中的引用,因为此操作将 的引用设置为新创建的 Person 对象中的引用?p = new Person();
c
p
我觉得这很难理解,因为如果我做这样的事情:
c.p = new Person();//(2)
这当然会影响参考。但是(1)和(2)有什么区别呢?c.p
答:
您的问题与按值传递*无关。您的问题是关于对象引用的。
每个变量在堆栈中都有一个给定的空间,并指向对象的引用,对吗?
你有太多的间接层次。:-)该变量包含对象引用。
将对象引用视为告诉 JVM 对象在内存中其他位置的位置。(这并不是字面上的正确,但这是一种非常好的思考方式。int
所以在:
c.p = p;
中的值(表示该对象所在位置的对象引用)被复制到 ,修改对象的状态。和 之间没有持续的关系,它们只是恰好暂时包含相同的值。p
Person
c.p
c
p
c.p
这就像我们在课堂上有一个,并且做了: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
但是(1)和(2)有什么区别呢?
在 (1) 中,您分配的是 ,而不是 ;这样做对 没有影响。在 (2) 中,您要分配给 .p
c.p
c.p
c.p
让我们按照第一个代码块进行操作,但我将使用 Java,而不是因为 Java 已经有一个类。执行此操作后:Container
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[]
数组,并且还有其他几个字段。
in 、 in 和 in 的字段只是为了表明它们包含引用;我们永远不会看到引用的实际值。Ref11325
c
Ref21354
p
Ref54312
p
name
然后,当您这样做时:
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”和“by-reference-by-reference”是艺术术语,与将变量传递到函数中时发生的情况有关:
doSomething(someVariable);
在 passby-value 中,将 的值传递到 。在传递引用中,对变量的引用被传递到 中。在传递引用中,函数可以伸出并更改变量的内容。在按值传递中,它不能。someVariable
doSomething
someVariable
doSomething
“pass-by-reference”中的“reference”一词与对象引用中的“reference”一词无关,它只是使用同一个重载词定义的两件事。传递引用中的“引用”是对变量的引用,而不是对象。
评论
执行此操作时:
Person p = new Person();
的实例是在堆上创建的(第一个实例),只要有东西引用它,它就存在。 是堆栈上的变量,其值是对该实例的引用。Person
p
执行此操作时:
p = new Person();
再次在堆上创建一个实例(第二个实例)。为局部变量分配对该实例的引用的新值。上一个实例(第一个实例)不再对它有任何引用,并且正在进行垃圾回收。Person
p
执行此操作时:
c.p = new Person();
再次在堆上创建一个实例(第三个实例)。当然,局部变量不受影响,因此“第二实例”也不受影响。局部变量还指向堆上的实例,该实例仍然是它的第一个实例。该实例包括一个名为 的类级变量,该变量现在遵循与上面第二个示例相同的规则。它以前指向堆上的实例,其值更新为指向新实例(第三个实例)。Person
p
c
Class
p
Person
这些变量中的“值”只是对象所在的堆中的一种地址。
在我准备OCA和OCP认证的过程中,我发现了一个非常好的建议--在纸上画出每个关系。
在操作 #5 中,您将删除在操作 #2 期间创建的连接(请参阅虚线)。
插图应该比任何文字解释更有帮助。希望这会有所帮助。
评论