很多程序设计语言(比如 C++、 Pascal )提供了两种参数传递的方式,值传递和引用传递
在正式介绍这两种类型之前,先说明下实参和形参的概念。
一、实参和形参
- 实参(实际参数,Arguments):用于传递给函数/方法的参数,必须有确定的值。
- 形参(形式参数,Parameters):用于定义函数/方法,接收实参,不需要有确定的值。
public class CountOnesInBinary {
//num为形参
public static int countOnesInBinary(int num) {
if (num == 0) {
return 0;
} else {
return num % 2 + countOnesInBinary(num / 2);
}
}
public static void main(String[] args) {
int num = 23;
//num为实参
int result = countOnesInBinary(num);
System.out.println("数字 " + num + " 的二进制格式中1的个数为: " + result);
}
}
二、值传递和引用传递
一般的程序设计语言提供了两种参数传递方式
- 值传递:方法接收的是实参值的拷贝,会创建副本。
- 引用传递方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
在java中只有值传递,基本数据类型传递的是值的副本,引用数据类型传递的是引用的副本。
- 对于基本数据类型(如int、double等),传递的是该数据值的一个副本,方法内对这些参数的修改不会影响到原始数据。
- 对于引用数据类型(如对象、数组等),传递的是引用变量(内存地址)的一个副本。虽然这有时被误解为“引用传递”,但实际上,由于传递的是引用的拷贝,所以它仍然遵循值传递的原则。这意味着在方法内部,你可以通过这个引用拷贝来修改所指向对象的内容,从而影响到原始对象,但是你不能改变引用本身让它指向另一个对象。
三、基本类型值传递代码示例
java的基本数据类型包括:
- 整型:byte, short, int, long
- 浮点型:float, double
- 字符型:char
- 布尔型:boolean
这里以int基本类型举例
package com.datastructures;
public class PrimitiveTypeExample {
public static void main(String[] args) {
int originalValue = 10;
System.out.println("调用前原始值: " + originalValue);
modifyPrimitive(originalValue);
System.out.println("调用后原始值: " + originalValue); // 值不变,依旧为10
}
static void modifyPrimitive(int value) {
value = 20; // 修改的是副本的值
System.out.println("方法内修改后的值: " + value);
}
}
可以看到基本类型值传递给方法后,在方法内部改变基本类型的值,不会影响到原始值
四、引用数据类型值传递
引用数据类型包括所有非基本类型的对象,如数组、类实例、接口实例等。
对于某些不可变的引用类型如Integer、String等,传递引用的副本到方法的形参上后,方法内部对引用副本指向的对象做操作,均不会影响原始引用。
代码示例如下:
package com.datastructures;
public class IntegerReferenceExample {
public static void main(String[] args) {
Integer originalInteger = 100; // 自动装箱
System.out.println("调用前Integer值: " + originalInteger);
modifyIntegerWrapper(originalInteger);
System.out.println("调用后Integer值: " + originalInteger); // 值保持不变,因为Integer是不可变的
System.out.println();
String s = "hello";
System.out.println("调用前String内容: "+s);
modifyString(s);
System.out.println("调用后String内容: "+s);
}
static void modifyIntegerWrapper(Integer num) {
num = 200; // 这里改变了num的引用,让它指向一个新的Integer对象,不影响原对象
System.out.println("方法内修改后的Integer值: " + num);
}
static void modifyString(String s) {
s = "world";
System.out.println("方法内修改后的String值: " + s);
}
}
执行结果可以看出不可变引传递给方法后,方法内部无论是把引用重新指向新对象,还是对引用本身指向的对象做操作,均不会对原始对象造成改变
对于某些可变的引用类型,比如List或者我们自定义的类实例对象,传递给方法的是原引用的副本,原引用和副本引用指向的对象相同,可以使用副本引用对对象本身做修改操作,但是方法内部如果直接把引用指向新的对象,原始对象仍然不会改变
下面的代码展示了这个特性:
package com.datastructures;
import java.util.List;
public class ReferenceTypeExample {
public static void main(String[] args) {
Integer[] originalArray = {1, 2, 3};
System.out.println("调用前数组内容: " + java.util.Arrays.toString(originalArray));
modifyArray(originalArray);
System.out.println("调用后数组内容: " + java.util.Arrays.toString(originalArray)); // 数组内容被修改
System.out.println("--------------------------------------------------------------------------------");
List<String> list = java.util.Arrays.asList("a", "b", "c");
System.out.println("调用前List内容: " + list);
modifyList(list);
System.out.println("调用后List内容: " + list);
}
static void modifyArray(Integer[] array) {
array[0] = 100; // 通过引用修改了原始数组的第一个元素
System.out.println("方法内修改后的数组内容: " + java.util.Arrays.toString(array));
}
static void modifyList(List<String> list){
list = java.util.Arrays.asList("x", "y", "z");
System.out.println("方法内修改后的List内容: " + list);
}
}
从执行结果可以看到,对于可变的引用类型,传递给方法后,对副本引用对象本身做修改操作,会影响原始对象,而把副本引用重新指向新的对象时,不会改变原始对象。