老生常谈的话题:Java 传参的方式到底是传值 还是传引用?
其实不同的角度理解都有道理,而接下来 先摆明我的立场:传值!
首先,来分析下Java中 "=" 的意义。
在"="的右边,是一个常量、表达式、或者对象(统统可以看成对象),在内存中会为它们分配空间。
而"="的左边,是一个引用(我们常说变量),保存的是右边对象的地址。
"="的实际意义就是 将右边对象的地址传给左边的引用,使得该引用指向了右边对象。
所以,Java里的引用可以看成是(C/C++所说的指针)。
另外,要明确一点:Java中都是通过使用引用来操作对象的。
为什么这么说呢?因为系统为"="右边的对象分配了空间,但是程序无法直接获取它,必须通过定义引用来得到。然后后面对该对象的操作都是通过操作该引用来实现。不管对于基本数据类型还是类对象,都是一样的处理流程。下面对基本数据类型和复杂的类对象进行说明:
(1)Java中创建基本数据类型变量的流程
int a = 3;
int b = 3;
编译器会首先创建一个变量名为a 的引用,然后在代码所在栈中查找是否有3这个常量值(如果没有,就在栈上开辟空间放入3),让a指向3。
接着编译器再创建一个变量名为b 的引用,在栈中发现已有3这个值,就让b也指向3。
这样,虽然a和b是不同的引用,但是都指向同一个值,a和b都是对这个值的引用。
(2)Java中创建复杂类型对象的流程
Person person1 = new Person("cb");
Person person2 = new Person("adm");
(1)基本数据类型 传参分析
public void badSwap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
//调用badSwap方法
int main(...) {
int a = 3, b = 5;
badSwap(a, b);
}
大家都知道这个方法是无效的。
main()方法中变量a是对常量3的引用,变量b是对常量5的引用。当a/b作为实参传递给badSwap方法时,传递的是a/b的拷贝(a'/b')。此时,a与a'指向3,b与b'指向5。经过badSwap方法处理后,改变的只是a'/b'的指向,并未影响到a/b。如图。
如果我们把传入的int型变量改为Object型结果也是一样的。改变的只是这份副本而已。就不分析了,下面分析复杂类对象的另一种情况。
(2)复杂类型的对象 传参分析
public void changeName(Person person)
{
person.name = "adm";
}
public static void main(String [] args)
{
Person person = new Person("cb","Shanghai");
System.out.println("Name: " + person.name + ", Address: " +person.address);
changeName(person);
System.out.println("Name: " + person.name + ", Address: " +person.address);
}
执行main方法,将得到以下输出:
Name: cb, Address: Shanghai
Name: adm, Address: Shanghai
结果:changeName方法成功改变了person的name值。为什么呢?
在main()方法中,person仅仅是对象的引用。当向changeName()传递person时,Java仅仅是传递了person这个引用的一个副本,即传向方法的引用实际上是原始引用的副本。这是一个传值操作(即副本值)。这样当Java传递对象引用的副本给方法后,就有两个引用指向了同一对象。
那为什么能成功改变 person 的值呢,如下图:
因为传值进来的是引用的副本,它也指向了原来的对象,虽然对副本的交换是无效的,但对副本所指向的对象的内部 进行的操作是会起作用的。图解很清晰了,引用所指向的对象的name属性指向了新的内存空间(存放着"adm")。