首先我们先给出结论:java中只有值传递,没有引用传递。对于引用数据类型,比如对象和数组,传递的都是对象引用的一个副本值而已。在java中,所有传递给方法的参数都是通过值传递的方式进行传递,我们可能经常提到"引用传递",但是,它传递的仍然是值,只不过这个值是对象引用的一个副本而已。
- 对于基本数据类型(byte、short、int、long、float、double、char、boolean),方法内部使用的是参数的副本,你对这个副本做任何操作,都不会改变原始的变量。
- 对于引用数据类型,传递的是对象引用的副本值而已。也就是说你在方法里面拿到的是指向同一个对象的指针,对对象的属性的更改会影响原始对象,但是对引用的重新赋值不会影响外部的引用。
下面我们用代码来进行举例说明:
1.值传递
在java中,所有传递给方法的参数都是通过值传递的方式进行传递。
- 当你传递的是基本数据类型的值时,实际上是传递了该值的一个副本。
- 当你传递的是引用类型时,实际上传递的是对象的引用(内存地址)的副本而已,并不是对象本身。
@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.步骤分析
-
创建对象:Person person = new Person("Alice");
这行代码在堆内存中分配了一块内存,创建了一个新的 Person 对象,其 name 属性初始化为 "Alice"。person 变量存储的是这个对象的引用(地址)。
-
传递引用:changeName(person);
方法 changeName 被调用,将 person 的引用(指向 "Alice" 这个对象)传递给参数 p。此时,p 和 person 都指向同一个堆内存中的对象。
-
修改对象内容:p.name = "Bob";
这行代码通过引用 p 修改了 Person 对象的 name 属性,使其现在变成 Bob。此时,person.name 的值也变为 "Bob",因为它们指向同一个对象。
-
重新赋值引用:p = new Person("Charlie");
这行代码做了以下两件事: 1、在堆内存中开辟了一块新空间,创建了一个新的 Person 对象,其 name 属性初始化为 "Charlie"。 2、将 p 的引用指向新的 Person 对象,而不再指向原来的对象(现在是 "Bob" 中的内容)。 2.影响
原始引用未改变:尽管 p 已经指向了一个新的对象("Charlie"),person 仍然保持不变,仍然指向原来的对象("Bob")。 原始对象的内容仍然有效:当输出 System.out.println(person.name); 时,将得到 "Bob"。
内存变化过程:
说明:
- 传递的是引用的副本。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 的指向。