在 Java 中,所有传递给方法的参数都是通过值传递的方式进行传递,尽管有时我们提到“引用传递, 然而在 Java 中并不存在真正的引用传递,只在讨论引用类型(如对象)时,我们常常提到“引用传递”。实际上,传递的仍然是值,但这个值是对象引用的副本. 在计算机科学中,值传递是将参数的实际值(或引用,对于对象)复制给被调用函数的一种方式。这意味着:
- 对于基本数据类型(如
int、char、boolean等),方法内部使用的是参数的副本。对这个副本的任何更改不会影响原始变量。 - 对于对象(引用类型),传递的是对象引用的副本。这意味着你在方法中获得的是指向同一对象的指针;对对象的属性的更改会影响原始对象,但对引用的重赋值不会影响外部的引用。
定义看起来有些枯燥? 那接下来使用代码进行解释:
目录
1. 值传递
定义
在 Java 中,所有传递给方法的参数都是通过值传递的方式进行传递。这意味着:
- 当你传递一个基本数据类型(如
int、char、boolean等)的值时,实际上是传递了该值的一份副本。 - 当你传递一个对象(引用类型),你传递的是对象的引用(内存地址)的副本,而不是对象本身。
示例:
public class ValuePassingExample {
public static void main(String[] args) {
int a = 10;
modifyValue(a); // 传递的是 a 的值的副本
System.out.println("a after modifyValue: " + a); // a 仍然是 10
}
public static void modifyValue(int number) {
number = number + 5; // 修改的是副本,不会影响原始 a
}
}
结果:
a after modifyValue: 10
总结:
- 基本类型的参数传递时,原变量的值不会受到方法内部的影响。
2. 引用传递
定义
- 当你传递一个对象引用时,传递的是引用的副本。方法内部对这个引用的修改会影响原始对象,因为它们指向的是同一个内存地址。
示例
class Person {
String name;
Person(String name) {
this.name = name;
}
}
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,分配新的堆内存
}
}
结果:
Person's name after modifyPerson: Bob
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");
- 这行代码做了以下两件事:
- 在堆内存中开辟了一块新空间,创建了一个新的
Person对象,其name属性初始化为"Charlie"。 - 将
p的引用指向新的Person对象,而不再指向原来的对象(现在是"Bob"中的内容)。
2.影响
- 原始引用未改变:尽管
p已经指向了一个新的对象("Charlie"),person仍然保持不变,仍然指向原来的对象("Bob")。 - 原始对象的内容仍然有效:当输出
System.out.println(person.name);时,将得到"Bob"。
总结
- 对象引用的参数传递时,原始对象的属性会受到影响;但是如果在方法内部重新赋值该引用,会导致原引用不变。
3. 内存分析
栈内存(main 方法栈帧) 栈内存(modifyValue 方法栈帧)
+-----------+ +-----------+
| a = 10 | ----> | number = 10|
+-----------+ +-----------+
| number = 15| <- modifyValue 中的 number 是 a 的副本
+-----------+
说明:
number在modifyValue方法中是a的副本,对number的更改不会影响a,因此在main方法中a的值仍然是10。
栈内存(main 方法栈帧) 堆内存
+---------------------+ +-----------+
| person (引用) | ----> | Person |
+---------------------+ | name = "Alice" |
+-----------+
(堆)|
|
栈内存(modifyPerson 方法栈帧) +-----------+
+-----------+ | Person |
| p (引用) | ---------------------------------------->| name = "Bob" | <- p.name 被修改
+-----------+ +-----------+
| p = new Person("Charlie"); // 这个引用是局部的,不影响 person|
+-----------+
说明:
- 传递的是引用的副本。
person在main方法和p在modifyPerson方法均指向同一个Person对象。 - 当我们通过
p.name = "Bob"修改对象的属性时,main方法中的person也受到了影响。 - 当
p被重新赋值为一个新的Person实例时,它只影响p的引用,不会改变main中的person引用。
4. 总结
- 所有的参数都是以值的形式传递(即副本),无论是基本数据类型还是引用类型。每个参数的传递都是创建一个新副本。这意味着,方法内部无法通过参数的副本来改变外部变量的引用,即: 对基本类型参数的更改不会影响外部变量.
- 对象内容的可变性:在方法中,我们可以修改对象的属性。这种修改会影响到原始对象,因为
p和person指向相同的对象。 - 引用的不可变性:尽管我们可以修改对象的内容,但如果在方法内部用新的对象引用赋值参数(即
p = new Person("Charlie");),这将不会影响原始引用person。这个赋值只会改变p所指向的对象,而不会改变person的指向。
1344

被折叠的 条评论
为什么被折叠?



