在 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
的指向。