协慌网

登录 贡献 社区

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

1
88250
贡献值 176
贡献次数 1

我一直以为 Java 传参时是传引用的

但是我看过的一些博客文章(例如这篇)却说 Java 是 “传值” 的。

我不太理解这些文章中所说的 “传值” 和 “传引用” 的区别。

有谁能再给我解释一下吗?

答案

Java 始终是按值传递的 。不幸的是,他们决定将对象的位置称为 “引用”。当我们传递一个对象的值时,我们将引用传递给它。这对初学者来说很困惑。

它是这样的:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

在上面的示例中, aDog.getName()仍将返回"Max" 。使用Dog "Fifi"在函数foo不更改main的值aDog ,因为对象引用按值传递。如果它是通过引用传递的,那么mainaDog.getName()将在调用foo后返回"Fifi"

同样:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

在上面的例子中, Fifi是调用foo(aDog)之后的狗的名字,因为对象的名字是在foo(...)food上执行的任何操作都是这样的,出于所有实际目的,它们是在aDog本身上执行的(除非d被更改为指向不同的Dog实例,例如d = new Dog("Boxer") )。

我刚刚注意到你引用了我的文章

Java 规范说 Java 中的所有内容都是按值传递的。在 Java 中没有 “pass-by-reference” 这样的东西。

理解这一点的关键是类似的东西

Dog myDog;

不是狗; 它实际上是指向狗的指针

这意味着,就在你拥有的时候

Dog myDog = new Dog("Rover");
foo(myDog);

你实际上是将创建的Dog对象的地址传递给foo方法。

(我说的主要是因为 Java 指针不是直接地址,但最容易想到它们)

假设Dog对象驻留在内存地址 42 处。这意味着我们将 42 传递给该方法。

如果方法被定义为

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

让我们来看看发生了什么。

  • 参数someDog设置为值 42
  • 在线 “AAA”
    • someDog之后到Dog它指向(在Dog对象在地址 42)
    • 要求Dog (地址为 42 的那个)将他的名字改为 Max
  • 在线 “BBB”
    • 创建了一只新Dog 。让我们说他在地址 74
    • 我们将参数someDog分配给 74
  • 在线 “CCC”
    • someDog 之后到Dog它指向(在Dog对象在地址 74)
    • 要求Dog (地址为 74 的那个)将他的名字改为 Rowlf
  • 然后,我们回来了

现在让我们考虑一下方法之外会发生什么:

myDog改变了吗?

关键是。

记住myDog是一个指针 ,而不是一个真正的Dog ,答案是否定的。 myDog的值仍为 42; 它仍然指向原始的Dog (但请注意,由于行 “AAA”,它的名称现在是 “Max” - 仍然是相同的狗; myDog的值没有改变。)

按照地址并改变最后的内容是完全有效的; 但是,这不会改变变量。

Java 的工作方式与 C 完全相同。您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据。但是,您无法更改指针指向的位置。

在 C ++,Ada,Pascal 和其他支持 pass-by-reference 的语言中,您实际上可以更改传递的变量。

要是 Java 传递 by-reference 语义中, foo我们在上面定义的方法会改变其中myDog指着时分配someDog上线 BBB。

将引用参数视为传入的变量的别名。分配该别名时,传入的变量也是如此。

Java 总是按值而不是通过引用传递参数。


让我通过一个例子解释一下:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

我将逐步解释这个:

  1. 声明名为参考f类型的Foo并将其分配给类型的新对象Foo与属性"f"

    Foo f = new Foo("f");

    在此处输入图像描述

  2. 从方法方面,声明了具有名称a的类型为Foo的引用,并且它最初被赋值为null

    public static void changeReference(Foo a)

    在此处输入图像描述

  3. 当您调用方法changeReference ,引用a将被分配给作为参数传递的对象。

    changeReference(f);

    在此处输入图像描述

  4. 声明名为参考b类型的Foo并将其分配给类型的新对象Foo与属性"b"

    Foo b = new Foo("b");

    在此处输入图像描述

  5. a = b将引用a NOT f重新分配给其属性为"b"的对象。

    在此处输入图像描述


  6. 当您调用modifyReference(Foo c)方法时,将创建引用c并将其分配给具有属性"f"的对象。

    在此处输入图像描述

  7. c.setAttribute("c");将更改引用c指向它的对象的属性,并且它是引用f指向它的同一对象。

    在此处输入图像描述

我希望你现在明白如何将对象作为参数传递在 Java 中:)