Java 是“传引用”还是“传值”?

更新于 2025-12-27

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 属性。food 所做的任何操作,在实际效果上都等同于对 aDog 所做的操作。但是,我们无法改变 aDog 这个变量本身的值(即它所指向的对象引用)

总结来说:
Java 中所有参数都是按值传递的。对于对象而言,传递的是对象引用的副本。因此,你可以通过这个引用来修改对象的状态,但不能让原始变量指向另一个对象。