java中到底是值传递还是引用传递

93 阅读4分钟

首先我们先给出结论:java中只有值传递,没有引用传递。对于引用数据类型,比如对象和数组,传递的都是对象引用的一个副本值而已。在java中,所有传递给方法的参数都是通过值传递的方式进行传递,我们可能经常提到"引用传递",但是,它传递的仍然是值,只不过这个值是对象引用的一个副本而已。

  1. 对于基本数据类型(byte、short、int、long、float、double、char、boolean),方法内部使用的是参数的副本,你对这个副本做任何操作,都不会改变原始的变量。
  2. 对于引用数据类型,传递的是对象引用的副本值而已。也就是说你在方法里面拿到的是指向同一个对象的指针,对对象的属性的更改会影响原始对象,但是对引用的重新赋值不会影响外部的引用。

下面我们用代码来进行举例说明:

1.值传递

在java中,所有传递给方法的参数都是通过值传递的方式进行传递。

  1. ​ 当你传递的是基本数据类型的值时,实际上是传递了该值的一个副本。
  2. ​ 当你传递的是引用类型时,实际上传递的是对象的引用(内存地址)的副本而已,并不是对象本身。
@Test
public void test() {
    int age = 18;
    System.out.println(age);//调用前是18
    modifyAge( age);
    System.out.println(age);//调用后还是18
}

public void modifyAge(int ageParam){
    ageParam = 20;//只是修改了副本
    System.out.println(ageParam);//20
}

我们来看一下上面代码的内存变化过程:

调用前:
test方法栈帧:age=18

调用modifyAge方法时:
test方法栈帧:age=18
modifyAge方法栈帧:ageParam=18(复制的值)

方法内修改后:
test栈帧:age=18
modifyAge方法栈帧:ageParam=20

modifyAge方法运行结束,modifyAge方法的栈帧就出栈,栈帧里面的内容就销毁了,修改就丢失了。

2.引用传递

当你传递一个对象引用(对象、数组)时,传递的是引用的副本。方法内部对这个引用的修改会影响原始对象,因为他们指向的是同一个内存地址。

public class ReferencePassingExample {
    public static void main(String[] args) {
        Person person = new Person("Alice"); // 1. 在堆中创建一个 Person 对象
        changeName(person); // 2. 将 person 的引用(指向 "Alice" 的对象)传递给 changeName
        System.out.println(person.name); // 输出 "Bob"  3. person 仍然指向原始对象
    }

    public static void changeName(Person p) {
        p.name = "Bob"; // 4. 修改了 p(指向原始对象)的内容
        p = new Person("Charlie"); // 5. 重新赋值 p,分配新的堆内存
    }
}
class Person {
    String name;

    Person(String name) {
        this.name = name;
    }
}

1.步骤分析

  1. 创建对象:Person person = new Person("Alice");

    这行代码在堆内存中分配了一块内存,创建了一个新的 Person 对象,其 name 属性初始化为 "Alice"。person 变量存储的是这个对象的引用(地址)。

  2. 传递引用:changeName(person);

    方法 changeName 被调用,将 person 的引用(指向 "Alice" 这个对象)传递给参数 p。此时,p 和 person 都指向同一个堆内存中的对象。

  3. 修改对象内容:p.name = "Bob";

    这行代码通过引用 p 修改了 Person 对象的 name 属性,使其现在变成 Bob。此时,person.name 的值也变为 "Bob",因为它们指向同一个对象。

  4. 重新赋值引用:p = new Person("Charlie");

​ 这行代码做了以下两件事: ​ 1、在堆内存中开辟了一块新空间,创建了一个新的 Person 对象,其 name 属性初始化为 "Charlie"。 ​ 2、将 p 的引用指向新的 Person 对象,而不再指向原来的对象(现在是 "Bob" 中的内容)。 2.影响

​ 原始引用未改变:尽管 p 已经指向了一个新的对象("Charlie"),person 仍然保持不变,仍然指向原来的对象("Bob")。 ​ 原始对象的内容仍然有效:当输出 System.out.println(person.name); 时,将得到 "Bob"。

内存变化过程:

image-20250919093055059.png

说明:

  • 传递的是引用的副本。person 在 main 方法和 p 在 modifyPerson 方法均指向同一个 Person 对象。
  • 当我们通过 p.name = "Bob" 修改对象的属性时,main 方法中的 person 也受到了影响。
  • 当 p 被重新赋值为一个新的 Person 实例时,它只影响 p 的引用,不会改变 main 中的 person 引用。

总结:

1.所有的参数,无论是基本数据类型还是引用类型(对象、数组),都是以值的形式传递的(即副本),每个参数的传递都是创建一个新副本。

2.对象内容的可变性:在方法中,我们可以修改对象的属性。这种修改会影响到原始对象,因为 p 和 person 指向相同的对象。

3.引用的不可变性:尽管我们可以修改对象的内容,但如果在方法内部用新的对象引用赋值参数(即 p = new Person("Charlie");),这将不会影响原始引用 person。这个赋值只会改变 p 所指向的对象,而不会改变 person 的指向。