一直没有搞清楚Java值传递究竟是什么意思,今天做《剑指Offer》第JZ40题时,给定了一个函数定义如public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)
,需要将结果直接存入数组num1
与num2
中。很纳闷,为什么Java值传递的机制,可以将外部传入方法中的参数在方法内修改后,对外部可见呢?于是发誓一定要弄清楚Java值传递的概念,查阅了很多资料,做如下总结。
一、JVM中的堆和栈
要说清值传递,还得先从JVM中的内存模型说起。简单的说一说JVM中的堆和栈。
- 堆内存:保存Java中的对象与数组(数组其实也是对象)。通过
new
出来的对象(实体)都保存在堆内存中,当实体失去引用后,该对象内存不会立马被释放,而是由JVM的GC定期回收。 - 栈内存:保存Java中的局部变量(如定义在方法中的、for循环中声明的循环变量等),当变量离开作用域后,内存会立即被释放,故栈刷新速度很快。
二、Java中的基本数据类型与引用类型
1 | int num = 10; |
如上所示代码中,num
是基本数据类型变量,其值10
直接保存在变量内部;str
是引用数据类型变量,其变量本身保存的是"Fan"
在堆内存中的地址,故说str
是字符串对象"Fan"
的一个引用。
三、为什么是值传递
1. 情况1:数组与基本数据类型
先定义下面两个方法:
1 | public void changeArray(int[] num) { |
在方法外部定义如下两个变量并使用方法:
1 | int[] numArray = {5, 4, 3}; |
得到的结果是numInt
依然是5
,而numArray
变成了{1, 4, 3}
。导致这种结果的分析如下:
numInt
传入changeInt()
方法时,将自身(实参)的值拷贝了一份赋给形参num
,在方法内部对形参的改变并不影响实参本身。numArray
传入changeArray()
方法时,将自身(实参)保存的值(也就是指向数组{5, 4, 3}
的地址)拷贝了一份赋给形参num
,形参持有相同的地址。在方法内部对形参的改变直接作用于其指向的地址上,故实参也会发生改变。
2. 情况2:字符串
1 | String str = "Fan"; |
如上代码执行后的结果是,str
依然为"Fan"
,而strBuf
变为了"Fan Jinpeng"
。分析:
- 在方法中,对字符串的重新赋值发生了
new
操作,即重新new
了一个"Jin"
对象,并由形参strChange
指向该对象地址,此时strChange
指向的地址已不再是由实参str
拷贝来的地址了,所以不会影响到实参(str
)保存的地址。 - 而对于
StringBuffer
类型的对象,若在方法内部进行append()
操作,形参strBuffer
指向的地址没有改变,还是由实参拷贝进来的,所以实参也会发生变化。
3. 情况3:对象
1 | class Person { |
如上代码执行后的结果是,fan.name
被改为了"Fan Jinpeng"
,而没有第二次被改变成"Spider Man"
。分析:
changeName()
方法将实参fan
引用指向的对象地址拷贝了一份给形参person
,方法内部的改动直接对该地址的指向,也即指向的对象进行修改(即对堆内存中存储的实体进行修改)。而newChangeName()
方法内部将形参重新指向了一个新的Person
对象,不再拥有拷贝来的地址,故对形参指向的对象的改动不会影响实参指向的对象。
四、不严谨的说
综上可以用不严谨的话来总结就是,Java是值传递,若方法传入的是对象,在方法内部对对象进行的直接修改是可以对外部发生改变的;然而若方法内部发生了new
操作,即改变了形参的地址指向,那么方法内部的操作就是针对新的对象做出的变化,不会影响外部的对象。