erlando 2024-09-05
“传值(pass-by-value)”和“传引用(pass-by-reference)”这两个术语在计算机科学中有特殊且精确定义的含义。这些定义与许多人初次听到这些术语时的直觉不同。讨论中的许多混淆似乎正是源于这一点。
“传值”和“传引用”描述的是变量的传递方式:
- 传值:将变量的值传递给函数或方法。
- 传引用:将指向该变量的引用(即变量本身的地址)传递给函数。这样,函数就可以修改该变量的内容。
根据上述定义,Java 始终是传值的。不幸的是,当我们处理持有对象的变量时,实际上是在处理被称为“引用(reference)”的对象句柄(handle),而这些引用本身也是以“传值”的方式传递的。这种术语和语义很容易让初学者感到困惑。
具体来看下面的例子:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// 我们将对象传递给 foo
foo(aDog);
// 当 foo(...) 返回后,aDog 变量仍然指向名为 "Max" 的狗
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// 在 foo() 内部,让 d 指向一个新的 Dog 实例,其 name 成员变量设为 "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
在这个例子中,aDog.getName() 仍然返回 "Max"。因为在 foo 中创建了一个新的 Dog 对象并赋值给参数 d,这并不会改变 main 方法中 aDog 变量的值。这是因为对象引用是以传值的方式传递的。如果 Java 是“传引用”的,那么调用 foo 后,main 中的 aDog.getName() 就会返回 "Fifi"。
再看另一个例子:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
foo(aDog);
// 当 foo(...) 返回后,狗的名字已经被改为 "Fifi"
aDog.getName().equals("Fifi"); // true
// 但它仍然是同一只狗:
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// 这里改变了 d 的名字为 "Fifi"
d.setName("Fifi");
}
在这个例子中,调用 foo(aDog) 之后,狗的名字变成了 "Fifi",因为我们在 foo(...) 内部修改了该对象的 name 属性。foo 对 d 所做的任何操作,在实际效果上都等同于对 aDog 所做的操作。但是,我们无法改变 aDog 这个变量本身的值(即它所指向的对象引用)。
总结来说:
Java 中所有参数都是按值传递的。对于对象而言,传递的是对象引用的副本。因此,你可以通过这个引用来修改对象的状态,但不能让原始变量指向另一个对象。