前言
对于Java初学者来说,刚学习Java的时候可能经常会听到调用方法时参数的值传递与引用传递。
但是,实际上Java中方法的参数传递机制只有值传递。 首先,我们要了解一个概念——栈帧。
栈帧位于java虚拟机栈中,用于支持虚拟机进行方法的调用和方法的执行。可以简单的理解为栈帧即方法。
每个方法有自己独立的栈帧。栈帧中有局部变量表、操作数栈、动态链接、返回地址等。
下面我们通过几个例子具体分析java方法的参数传递机制。
方法的参数为基本数据类型
首先我们来看下面代码:
public class ParamTransmit { public static void main(String[] args) { int i = 1; change(i); System.out.println("i = " + i); } public static void change(int j) { j += 1; } }
程序的运行结果为:
当在main方法中调用change方法时,参数传递相当于将main栈帧中的参数i=1拷贝了一份给change栈帧,此时change栈帧中,变量j的值为1。然后在change方法中执行j java基础的传递性 += 1;运算,j的值变为2,change方法结束。main方法未结束,此时main栈帧中变量i的值仍为1。当传递的参数是基本数据类型时,传递的是数据值,即将参数拷贝一份到被调用方法的栈帧中。该方法中对变量的操作不影响原来栈帧中的变量的值,原栈帧中的变量不被修改。
方法的参数为引用类型
引用类型变量如String类型,包装类,数组和其他自定义类等。下面依次介绍。
String类型
先上代码
public class ParamTransmit { public static void main(String[] args) { String str = "hello"; change(str); System.out.println("str = " + str); } public static void change(String str) { str += "world"; } }
程序运行结果为:
首先,在jdk1.7之后,运行时常量池从方法区中移了出来,在堆中开辟了一块区域存放运行时常量。
并且要知道String的值是不可变的,每次对String的操作都会产生新的String对象。
因此,在上面代码中,main方法中调用change方法时,首先是将变量str的值(即运行时常量池中hello的地址)拷贝一份到change栈帧中,此时change栈帧中的变量str也是指向“hello”。
然后执行str += "world";操作,由于String值不可变,此时change栈帧中的str指向新的字符串“helloworld”,str的地址值已改变。
而此时main栈帧中str的值并没有改变,仍指向“hello”,所以main方法中输出的str仍是“hello”。
包装类
先上代码:
public class ParamTransmit { public static void main(String[] args) { Integer num = 200; change( num); System.out.println("num = " + num); } private static void change(Integer num) { num += 1; } }
程序运行结果为:
上面代码中,首先Integer num = 200;会自动装箱,先调用Integer.valueOf(200),在堆中创建一个值为200的Integer实例,并将该实例的地址赋值给num。
main方法在调用change方式时传递的是num的地址值,此时change栈帧中的num也指向值为200的Integer实例。
当执行num += 1;时,也自动装箱,先调用Integer.valueOf(201),在堆中创建一个职位201的Integer实例,并将该实例的地址赋值给change栈帧中的变量num,change方法结束。
但此时,main栈帧的num仍指向值为200的Integer实例,因此打印结果为200。
内存结构图如下:
注:上述内存结构图仅适用于不在-128至127之间的整数。Integer类的自动装箱机制是首先提供一个Integer cache[],用于存放-128至127的缓存。因此,若对实参及对形参操作的结果均在-128至127之间,则实参及形参应指向数组中的元素,而不是堆中新的Integer实例。
数组
直接上代码
import java.util.Arrays; public class ParamTransmit { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; change(arr); System.out.println("arr = " + Arrays.toString(arr)); } private static void change(int[] arr) { arr[0] += 1; }
程序运行结果为:
数组变量也是引用类型。main方法在调用change方法时,传递的是数组arr的地址值。
change方法内,将数组中第一个元素的值自增1,此时数组中第一个元素的值变为2。
change方法结束,main方法继续执行,此时main栈帧变量arr仍指向该数组,只不过该数组中的元素已被修改。所以输出的是被修改后的数组。
内存结构示意图如下:
可以引申为多维数组及数组元素为引用类型的情况。
其他自定义类
直接上代码
class MyData { int a = 10; } public class ParamTransmit { public static void main(String[] args) { MyData md = new MyData(); change(md); System.out.println("md.a = " + md.a); } private static void change(MyData md) { md.a += 1; } }
程序运行结果为:
main方法调用change方法时,同样是将main栈帧中的变量md的值传递到change栈帧中,此时传递的是地址值,指向堆中同一个MyData实例。
然后执行md.a += 1,此时该实例的成员变量a为11。
change方法执行完毕,main方法未结束,main栈帧中的变量md仍指向该实例,此时输出的a为11。内存结构如下图。
若将上面代码修改如下,会是什么结果呢?
class MyData { int a = 10; } public class ParamTransmit { public static void main(String[] args) { MyData md = new MyData(); change(md); System.out.println("md.a = " + md.a); } private static void change(MyData md) { md = new MyData(); md.a += 1; } }
程序运行结果为:
main方法调用change方法时,同样传递的是地址值。
然而执行md = new MyData();时,在堆中创建了一个新的实例。
此时change栈帧中的变量md的值变成了新的MyData实例的地址。
然后继续执行md.a += 1;,修改的是新的实例的成员变量。
当change方法调用结束后,main方法继续执行,此时main栈帧中md指向的仍是原来的实例,原实例的成员变量未被修改,仍是10。因此输出结果是10。
内存结构如下图。
总结
通过以上分析,我们可以知道java的参数传递机制是值传递。
若参数是基本数据类型,传递的是数据值;若参数是引用数据类型,传递的是地址值。
若参数是基本数据类型,对形参的操作不影响实参,因其是不同栈帧的不同变量。
若参数是引用数据类型,并且是String、包装类等。
因其对象的不可变性,对形参的操作会导致其指向新的String或包装类等对象,但不影响实参,实参仍指向原来的对象且该对象并未被修改。
若参数是引用数据类型,并且是数组、StringBuffer及其他自定义类等时,对形参的操作会影响到实参,因其指向的是同一个实例。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/24772.html