如何理解Java中的引用传递和值传递?
想必,大家都或多或少的听过Java都是值传递这句话吧,那为什么呢?
首先,想要区分两者,必须得知道什么是引用传递,什么是值传递:
值传递(pass by value):是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference):是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
参数在程序语言中又分为形参和实参:
形式参数:是在定义函数名和函数体时使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
举个栗子:
public static void main(String[] args){
Example example = new Example();
int i = 10;
example.show(i)
System.out.println("输出的i为:"+i);
}
public void show(int j){
j = 20;
System.out.println("输出的j为:"+j);
}
在show方法中修改了参数j的值,然后分别在main和show方法中打印了参数的值。输出如下:
输出的j为:20
输出的i为:10
很明显,show方法没有对 j 的值的修改没有改变实际参数 i 的值。但不能只用基本类型,也可使用引用类型来举例:
public static void main(String[] args) {
Example example = new Example();
User userA = new User();
userA.setName("Tom");
userA.setAge(25);
example.show(userA);
System.out.println("main输出:"+userA);
System.out.println("main userA的name:"+userA.getName()+" age:"+userA.getAge());
}
public void show(User user){
user.setName("Jack");
user.setAge(19);
System.out.println("show输出:"+user);
System.out.println("show user的name:"+user.getName()+" age:"+user.getAge());
}
在show方法中修改形参的值,输出如下:
show:User@2f2c9b19
show user的name:Jack age:19
main输出:User@2f2c9b19
main userA的name:Jack age:19
可以看到它们的引用地址和值都是一致的。有人就开始说,这不就是引用传递吗?先别急在往下看:
public static void main(String[] args){
Example example = new Example();
String str = "abcde";
example.show(str);
System.out.println("main print:"+str);
}
public void show(String s){
s = "ABCDE";
System.out.println("show print:"+s);
}
输出结果为:
show print s:ABCDE
main print:abcde
这个时候就会发现,同样传递了一个对象,但是原始参数的值并没有被修改。
那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。
值传递 | 引用传递 | |
---|---|---|
区别 | 会创建副本 | 不创建副本 |
结果 | 函数中无法改变原始对象 | 函数中可以改变原始对象 |
举个实例:
你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在show方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙,而是钥匙打开的房子。
当在第二个栗子中的show方法里加一句代码:
user = new User();
输出的结果就不一样了。
show:User@2f2c9b19
show user的name:Jack age:19
main输出:User@776ec8df
main userA的name:Tom age:25
我来解释一下:当在main中创建一个User对象时,在堆中开辟了一块内存,其中保存了name和gender等数据。然后userA持有该内存地址0x111111
当调用show方法,并且已userA作为实参传递给形参参数user的时候,会把这个地址交给user。
在show中对参数修改是,即user = new User(),在执行这个语句时,会重新开辟一块0x222222的内存,赋值给user。后面对user的任何修改都不会改变内存0x111111的内容。
上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在执行user = new User();的时候,实际参数的引用也应该改为指向0x222222,但是实际上并没有。
通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。
而另一个栗子中的show方法中没有new一个新的User对象,它的引用仍然指向0x111111,在进行改值的时候,当然会对其中的值发生改变。
**所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。**在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。
总结
无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。
按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
**而按共享传递其实只是按值传递的一个特例罢了。**所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。
需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
**而按共享传递其实只是按值传递的一个特例罢了。**所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。