前言
对于Java参数是传值还是传引用这个问题,大家总是众说纷纭,在《Thinking in Java》中是这么解释的:When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.(对于基本类型变量,Java是传值的副本;对于所有的引用类型变量,如String等,Java都是传引用的副本)这里我们首先需要明确下什么是基本类型变量,什么是引用类型变量。
- 基本类型变量
- 整型:byte,short,int,long
- 浮点型:float,double
- 字符型:char
- 布尔型:boolean
- 引用类型变量
- 数组
- 类
- 接口
代码说明
接下来将三个代码示例对Java参数是传值还是传引用进行详细的说明。
示例代码1:
public class Test1 {
public static void main(String[] args) {
int a = 1;
System.out.println("Before test(int) : a = " + a);
test(a);
System.out.println("After test(int) : a = " + a);
}
public static void test(int a) {
a++;
System.out.println("In test(int) : a = " + a);
}
}
运行结果 :
Before test(int) : a = 1
In test(int) : a = 2
After test(int) : a = 1
不难看出,虽然在test(int)方法中改变了传进来的参数值,但这个对参数的源变量本身(即对main(String[])方法中的a变量)并没有影响,说明在传递基本类型变量时,实际上是将参数值作为一个副本传进方法函数的,在调用的方法函数中,不管怎么改变这个值,其结果都只是改变了副本的值,而不会影响到源值。
流程:
- 主函数进栈,a初始化。
- 调用test(int)方法,test(int)方法进栈,将main(String[])中a的值,复制一份给test(int a)中的a。
- test(int)方法中对a的值进行a++自增运算。
- test(int)方法运行完毕,a的值加1。
- test(int)方法弹栈。
- 主函数弹栈。
示例代码2:
public class Test2 {
public static void main(String[] args) {
StringBuffer string = new StringBuffer("Hello");
System.out.println("Before test(StringBuffer) : string = " + string);
test(string);
System.out.println("After test(StringBuffer) : string = " + string);
}
public static void test(StringBuffer str) {
str.append(", world!");
System.out.println("In test(StringBuffer) : str = " + str);
}
}
运行结果:
Before test(StringBuffer) : string = Hello
In test(StringBuffer) : str = Hello, world!
After test(StringBuffer) : string = Hello, world!
在main(String[])中调用了test(StringBuffer)方法,并将string作为参数进行传递。这里的string是一个对象引用,Java实际上将它的副本传进方法函数的,这个函数里面的引用副本指向的是对象的地址,通过引用副本找到地址并修改地址中的值,也就改变了对象。
流程:
- 主函数进栈,StringBuffer string初始化。
- 调用test方法,test( )进栈,将string指向的地址值,复制一份给str。
- test方法中,根据地址值,找到堆中的StringBuffer对象,并将它的值改为“Hello,world!”。
- test方法运行完毕,StringBuffer的值已经改变。
- test方法弹栈。
- 主函数弹栈。
示例代码3:
public class Test3 {
public static void main(String[] args) {
String string = "Hello";
System.out.println("Before test(StringBuffer) : string = " + string);
test(string);
System.out.println("After test(StringBuffer) : string = " + string);
}
public static void test(String str) {
str = "Hello, world!";
System.out.println("In test(StringBuffer) : str = " + str);
}
}
运行结果:
Before test(StringBuffer) : string = Hello
In test(StringBuffer) : str = Hello, world!
After test(StringBuffer) : string = Hello
也许大家会觉得奇怪,类比示例代码2,同样是将引用类型作为参数传进方法函数中,为什么示例代码2能够改变对象的值,而示例代码3中却不行?首先,我们需要清楚的一点,在String的API中有这么一句话:their values cannot be changed after they are created,即String类是final类型的,String的值在创建之后不能被更改。所以在执行str = "Hello, world!";时,其过程为:首先系统会自动生成一个新的String对象,并把这个新对象的值设为“Hello,world!”,然后把这个对象的引用赋给str。既然对象都是新的,那么就与原来的“Hello”没有任何关系。因此,当函数结束,引用副本str的作用消失,原来内存地址上的内容并没有任何的改变,打印结果仍是Hello。而在示例代码2中,str.append(", world!");就不同了,StringBuffer是产生一块内存空间,相关的增删改都在其中运行,所以添加一句“,world!”仍然是在同一块内存地址上进行,str所指向的引用并没有改变。
流程:
- 主函数进栈,string初始化。
- 调用test方法,test( )进栈,将string指向的地址值,复制一份给str。
- test方法中,重现创建了一个String对象”Hello,world!”,并将str指向了新的地址值。
- test方法运行完毕,str所指向的地址值已经改变,但string指向的地址值不变。
- test方法弹栈。
- 主函数弹栈。
小结
对于Java参数是传值还是传引用这个问题,对于基本类型变量,Java是传值的副本;对于引用类型变量,即所有的对象型变量,Java都是传引用的副本。
PS:上述如果有任何理解错误的地方,欢迎大家批评指出。