Java 方法实参是传值(pass-by-value)还是传引用(pass-by-reference)?

Java
347
0
0
2022-12-09

本文最后更新于 232 天前,其中的信息可能已经有所发展或是发生改变。

传值还是传引用?

一直以来,我认为在 Java 中传递方法实参时,当传入的类型为基本数据类型时,则传入的是值;当传入的类型为对象时,则传入的是引用。但是,这个观念是实际上是错误的。事实上,Java 始终传递值

为什么这么说呢,让我们看一下如下代码:

public static void main(String[] args) {
    ...
    int y = 5;
    System.out.println(y); // prints "5"
    myMethod(y);
    System.out.println(y); // prints "5"
}

public static void myMethod(int x) {
    ...
    x = 4; // myMethod has a copy of x, so it doesn't 
           // overwrite the value of variable y used 
           // in main() that called myMethod
}

相信大家都很容易理解这段代码,因为整型变量 x 传入 myMethod 时传入了值,而不是引用,因此即使我们设置 x 的值,也不会导致方法外的 y 有任何改变。

通常来说,我们认为 Java 对对象传递引用的原因是当我们在方法内修改一个对象内部的状态时,(与 C++ 直接传递对象类型时不同,)这个对象的值的改变会传递到外部,就像这样:

public class Cat {
    private String name;

    // Setter 
    public void setName(String name) {
        this.name = name;
    }

    // Getter 
    public void getName() {
        return this.name;
    }
}

public class Example {
    public static void adoptCat(Cat c, String newName) {
        c.setName(newName);
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        myCat.setName("Charlotte");
        System.out.println(myCat.getName()); // prints "Charlotte"

        adoptCat(myCat, "Lydia");
        System.out.println(myCat.getName()); // prints "Lydia"
    }
}

但是,如果我们尝试像开头的代码一样,试图直接修改对象类型实参,这些修改会生效吗?

public static void main(String[] args) {
        String str = "Hello";
        System.out.println(str); // prints "Hello" 
        myMethod(str);
        System.out.println(str); // prints "Hello"
    }

    public static void myMethod(String strIn) {
        strIn = "World";
    }

事实上我们会发现,和值类型一样,外部的 str 不会因为 strIn 实参的修改产生任何改变。如果 Java 对传入的对象类型的是传引用的话,那么当我们修改引用时,这个更改就应该被应用到外部,但实际上并没有。

那么,到底是什么

事实上我们清晰的知道 Java 的对象类型是引用类型,那么,方法实参传入的“值”,到底是什么?

经过一番查找,我在这篇文章中得到了答案:

Java is officially always pass-by-value. The question is, then, “what is passed by value?” As we have said in class, the actual “value” of any variable on the stack is the actual value for primitive types (int, float, double, etc) or the reference for reference types. That is, for a reference variable, the value on the stack is the address on the heap at which the real object resides. When any variable is passed to a method in Java, the value of the variable on the stack is copied into a new variable inside the new method.

事实上,Java 在传递引用类型时,传入的不是引用类型本身,而是引用类型的引用,对于这个引用类型的引用,Java 是按值传递的,这也就是意味着,Java 事实上从栈区域复制了一份对象的引用,传递给了方法。

img

img

因此,当我们试图修改传入的这个复制份的“引用类型的引用”时,原本的引用并不会产生任何改变。

如果说拿 C++ 代码来表示的话,实际上是这个样子:

void func(Object* raw);

传入的东西其实是对象引用,然后,Java 相当于会自行解引用,然后就得到了我们事实上传入的那个对象实例。

最后,我们终于得知了 Java 方法参数传值的秘密:对于基本数据类型,直接传递值;对于对象类型(引用类型),将其引用(值)复制一份后再传入。