实参与形参
**形式参数:**是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
**实际参数:**在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
public static void main(String[] args) {
ParamTest pt = new ParamTest();
pt.sout("Hollis");//实际参数为 Hollis
}
public void sout(String name) { //形式参数为 name
System.out.println(name);
}
值传递与引用传递
值传递:在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
举个例子方便理解:
有一把钥匙,当别人去你家时,你直接就把钥匙给他了,这就是引用传递,在这期间他可能会对你的钥匙做些什么,比如刻上名字等等,归还钥匙时,钥匙上就会有他刻的名字,这是引用传递。
有一把钥匙,当别人取你家时,你去打印社复刻了一把新钥匙给他,自己的钥匙还在手里,无论他对钥匙做什么都不会影响你手中的钥匙,这就是值传递。
java只有值传递
前言:
错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
错误理解二:Java是引用传递。
错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
求值策略:
分为两大基本类,基于如何处理给函数的实际参数,分为严格的和非严格的。
在严格求值中有几个关键的求值策略是我们比较关心的,那就是传值调用(Call by value)、传引用调用(Call by reference)以及传共享对象调用(Call by sharing)。
***传值调用(值传递):***在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。所以被调用函数改变形参值,并不会改变实参的值。
***传引用调用(引用传递):***在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
***传共享对象调用(共享对象传递):***传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调用函数形参,因为参数地址都指向同一个对象,所以我们也称为“传共享对象”,所以如果被调函数中改变形参的值,调用者是可以看到这种变化的。
共享对象传递和值传递过程几乎一样,都是求值,拷贝,传递。共享对象传递和引用传递调用的结果又一样,当被调函数若改变参数内容,那么这种改变也会对调用者有影响。
对于该问题应关注过程而非结果,因为值传递与共享对象传递过程一样,而且都有一步关键操作就是“复制”,因此,通常我们认为传共享对象调用是传值调用的特例。
java求值策略:
很多人误认为java中的对象传递是引用传递,主要是因为java中变量和对象之间是有引用关系的,java语言中是通过对象的引用来操纵对象的,很多人举出以下代码:
public static void main(String[] args) {
Test pt = new Test();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
结果:
print in pass , user is User{name=‘hollischuang’, gender=‘Male’}
print in main , user is User{name=‘hollischuang’, gender=‘Male’}
可以看到,对象类型在被传递到pass方法后,在方法内改变了其内容,最终调用方main方法中的对象也变了。所以,很多人说,这和引用传递的现象是一样的,就是在方法内改变参数的值,会影响到调用方。
我们前面说过,无论是值传递,还是引用传递,只不过是求值策略的一种,那求值策略还有很多,比如前面提到的共享对象传递的现象和引用传递也是一样的。那凭什么就说Java中的参数传递就一定是引用传递而不是共享对象传递呢?
那么,Java中的对象传递,到底是哪种形式呢?其实,还真的就是共享对象传递。
《The Java™ Tutorials》关于对象传递描述:
Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.
也就是说,引用数据类型参数(如对象)也按值传递给方法。这意味着,当方法返回时,传入的引用仍然引用与以前相同的对象。但是,如果对象字段具有适当的访问级别,则可以在方法中更改这些字段的值。
其实Java中使用的求值策略就是传共享对象调用,也就是说,Java会将对象的地址的拷贝传递给被调函数的形式参数。
值传递和共享对象传递不冲突
在参数传递过程中,实际参数的地址被拷贝给了形参,这个过程是值传递,只不过传递的值的内容是对象的引用。
举个例子,就像你复制了一把你家里的钥匙给到你的朋友,他拿到钥匙以后,并没有在这把钥匙上做任何改动,而是通过钥匙打开了你家里的房门,进到屋里,把你家的电视给砸了,这个过程,对你手里的钥匙来说,是没有影响的,但是你的钥匙对应的房子里面的内容却是被人改动了。
所以,可以说,java的对象的传递是通过复制的方式把引用关系传递了,如果我们没有改引用关系,而是找到引用地址,把里面内容改了,是会对调用方法有影响的,因为大家指向的是同一个共享对象。
再改动下pass方法:
public void pass(User user) {
//重新new了一个user对象,并改变它的值
user = new User();
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
结果:
print in pass , user is User{name=‘hollischuang’, gender=‘Male’}
print in main , user is User{name=‘Hollis’, gender=‘Male’}
这个过程,就好像你复制了一把钥匙给到你的朋友,你的朋友拿到你给他的钥匙之后,找个锁匠把他修改了一下,他手里的那把钥匙变成了开他家锁的钥匙。这时候,他打开自己家,就算是把房子点了,对你手里的钥匙,和你家的房子来说都是没有任何影响的。
总结:
在程序设计中,求值策略有很多种,比较常见的就是值传递和引用传递。还有一种值传递的特例——共享对象传递。
值传递和引用传递最大的区别是传递的过程中有没有复制出一个副本来,如果是传递副本,那就是值传递,否则就是引用传递。
在Java中,其实是通过值传递实现的参数传递,只不过对于Java对象的传递,传递的内容是对象的引用。
绝对不能认为Java中有引用传递。