Java中的参数传递

时间:2021-03-10 21:26:07

实际Java编程过程中,我们会发现,有些参数传入到函数后,经过函数的修改后,原值被改变了,而有些则不会改变原值,区别到底在哪呢?

看两段代码,结合这两段代码来分析这个问题:

public class ParameterTransfer {
public static void main(String[] args) {
int num = 30;
System.out.println("调用add方法前num=" + num);
add(num);
System.out.println("调用add方法后num=" + num);
}

public static void add(int param) {
param = 100;
}
}
public class ParameterTransfer {     public static void main(String[] args) {          String[] array = new String[] {"huixin"};          System.out.println("调用reset方法前array中的第0个元素的值是:" + array[0]);          reset(array);          System.out.println("调用reset方法后array中的第0个元素的值是:" + array[0]);     }       public static void reset(String[] param) {          param[0] = "hello, world!";     }}   


第一段函数最后输出的是30,原值没有被改变;

第二段函数最后输出的是"hello, world",原值被改变了。


先说结论:

Java编程语言只有一种参数传递方式,就是值传递,这两个例子当然也就都属于值传递。

如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了


副本的值不会改变原始的值。


如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么


在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的地址,所以不会改变


参数的值。


说完结论后我们来分析一下是什么原因,无论是什么语言,要讨论参数传递方式,就得从内存模型说起,主要是我个人觉得从内存模型来说参数传递更为直观一
些。闲言少叙,下面我们就通过内存模型的方式来讨论一下Java中的参数传递。
这里的内存模型涉及到两种类型的内存:栈内存(stack)和堆内存(heap)。基本类型作为参数传递时,传递的是这个值的拷贝。无论你怎么改变这个拷贝,原值是
不会改变的。看下第一段代码,然后结合内存模型来说明问题:

下边通过内存模型来分析一下。

当执行了int num = 30;这句代码后,程序在栈内存中开辟了一块地址为AD8500的内存,里边放的值是30,内存模型如下图:

Java中的参数传递

执行到add()方法时,程序在栈内存中又开辟了一块地址为AD8600的内存,将num的值30传递进来,此时这块内存里边放的值是30,执行param = 100;后,

AD8600中的值变成了100。内存模型如下图:   

Java中的参数传递

地址AD8600中用于存放param的值,和存放num的内存没有任何关系,无论你怎么改变param的值,实际改变的是地址为AD8600的内存中的值,而AD8500

中的值并未改变,所以num的值也就没有改变。

然后我们再看第二段代码:

当对象作为参数传递时,传递的是对象的引用,也就是对象的地址。下边用内存模型图来说明。

Java中的参数传递

当程序执行了String[] array = new String[] {"huixin"}后,程序在栈内存中开辟了一块地址编号为AD9500内存空间,用于存放array[0]的引用地址,里边

放的值是堆内存中的一个地址,示例中的值为BE2500,可以理解为有一个指针指向了堆内存中的编号为BE2500的地址。堆内存中编号为BE2500的这个地址中

存放的才是array[0]的值:huixin。

当程序进入reset方法后,将array的值,也就是对象的引用BE2500传了进来。这时,程序在栈内存中又开辟了一块编号为AD9600的内存空间,里边放的值是传
递过来的值,即AD9600。可以理解为栈内存中的编号为AD9600的内存中有一个指针,也指向了堆内存中编号为BE2500的内存地址,如图所示:
Java中的参数传递

这样一来,栈内存AD9500和AD9600(即array[0]和param的值)都指向了编号为BE2500的堆内存。

在reset方法中将param的值修改为hello, world!后,内存模型如下图所示:

Java中的参数传递
改变对象param的值实际上是改变param这个栈内存所指向的堆内存中的值。param这个对象在栈内存中的地址是AD9600,里边存放的值是BE2500,所以堆
内存BE2500中的值就变成了hello,world!。程序放回main方法之后,堆内存BE2500中的值仍然为hello,world!,main方法中array[0]的值时,从栈内存中找
到array[0]的值是BE2500,然后去堆内存中找编号为BE2500的内存,里边的值是hello,world!。所以main方法中打印出来的值就变成了hello,world!

再总结一下:

无论是基本类型作为参数传递,还是对象作为参数传递,实际上传递的都是值,只是值的的形式不同而已。第一个示例中用基本类型作为参数传递时,将栈内存中

的值30传递到了add方法中。第二个示例中用对象作为参数传递时,将栈内存中的值BE2500传递到了reset方法中。当用对象作为参数传递时,真正的值是放在

堆内存中的,传递的是栈内存中的值,而栈内存中存放的是堆内存的地址,所以传递的就是堆内存的地址。这就是它们的区别。


但是,有一个需要注意的地方

在Java中,String是一个引用类型,但是在作为参数传递的时候表现出来的却是基本类型的特性,即在方法中改变了String类型的变量的值后,不会影响方法外
的String变量的值。