Java中的形参和实参的区别以及传值调用和传引用调用
1.名词解析:
1.形参:用来接收调用该方法时传递的参数。只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间。因此仅仅在方法内有效。
2.实参:传递给被调用方法的值,预先创建并赋予确定值。
3.传值调用:传值调用中传递的参数为基本数据类型,参数视为形参。
4.传引用调用:传引用调用中,如果传递的参数是引用数据类型,参数视为实参。在调用的过程中,将实参的地址传递给了形参,形参上的改变都发生在实参上。
案例分析:
1】.基础数据类型(传值调用)
传值,方法不会改变实参的值。
2】.引用数据类型(引用调用)
传引用,方法体内改变形参引用,不会改变实参的引用,但有可能改变实参对象的属性值。
举两个例子:
(1)方法体内改变形参引用,但不会改变实参引用 ,实参值不变。
public class TestFun2 {
public static void testStr(String str){
str="hello";//型参指向字符串 “hello”
}
public static void main(String[] args) {
String s="1" ;
TestFun2.testStr(s);
System.out.println("s="+s); //实参s引用没变,值也不变
}
}
执行结果打印:s=1
(2)方法体内,通过引用改变了实际参数对象的内容,注意是“内容”,引用还是不变的。
public class TestFun4 {
public static void testStringBuffer(StringBuffer sb){
sb.append("java");//改变了实参的内容
}
public static void main(String[] args) {
StringBuffer sb= new StringBuffer("my ");
new TestFun4().testStringBuffer(sb);
System.out.println("sb="+sb.toString());//内容变化了
}
}
执行结果,打印:sb=my java 。
2.测试用例1
方法传参时,Java并没有使用传引用的方式,而是采用了传值的方式。
(1)例如下面的badSwap()方法:
public void badSwap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
当badSwap方法时,原有的var1和var2的值并不会发生变化。
(2)详例
public void tricky(Point arg1, Point arg2)
{
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
}
public static void main(String [] args)
{
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
System.out.println("X: " + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
}
执行main()的输出如下:
X: 0 Y: 0
X: 0 Y: 0
X: 100 Y: 100
X: 0 Y: 0
这个方法成功地改变了pnt1的值,但pnt1和pnt2的交换却失败了!这是Java参数传递机制里最让人迷惑的地方。在main()中,pnt1和pnt2是Point对象的引用,当将pnt1和pnt2传递给tricky()时,Java使用的正是传值的方式,将这两个引用的传给了arg1和arg2。也就是说arg1和arg2正是pnt1和pnt2的复制,他们所指向的对象是相同的。在main()中,引用被复制并以传值的方式进行传递,对象本身并不会被传递。因此,tricky()方法中pnt1所指向的对象发生了变化。因为传递的是引用的复制,因此引用的交换既不能引起对象的交换,更不会使原始引用发生变化。如图2所示,tricky()交换了arg1与arg2,但不会影响pnt1和pnt2。
3.测试用例2:
现在此举出例子并加以说明
public class Aclass {
int data;
}
该类为作为测试用的类,里面只有一个域。
3.1第一个用例:
public class TestDemo {
public static void main(String[]args) {
Aclass a = new Aclass();
a.data = 10;
test1(a);
System.out.println(a.data);
}
public static void test1(Aclass aclass) {
aclass.data = 0;
}
执行过程:
先构造一个新的实例,并为其赋值为10。再调用方法test1(),在该方法中将data的值设为0。然后程序又回到方法体外面,输出结果为0。
3.2第二个用例:
public class TestDemo {
public static void main(String[]args) {
Aclass a = new Aclass();
a.data = 10;
test2(a);
System.out.println(a.data);
}
public static void test2(Aclass aclass) {
Aclass b = new Aclass();
b.data = 100;
aclass = b;
}
}
此时同样构造一个新的实例,并将其数据域赋值为10,再调用方法test2()。与方法test1()不同的是,在该方法中又定义了一个新的实例(其值为100),然后使传入的参数指向该实例。该方法执行完毕后,输出的结果为10。
运行过程说明
3.1.1 对于第一个测试用例
构造实例并为其数据赋值后(如下图),产生一个指向实例的引用a:
调用方法test1()时(如下图),a’为引用a的一个拷贝,它也指向当前的实例:
当在test1()的方法体中修改数据域的值时(如下图):
因为在方法体中,传入的引用的拷贝指向没有发生改变,所以它修改了数据域的值后会影响到原来的实例的值。
3.2.1 对于第二个测试用例:
同样,构造实例并为其赋值后,再调用方法test2()。在方法体中传入的依然是引用的一个拷贝。如下图:
在方法test2()中构造一个新的实例并为其赋值100后,如下图:
在方法体中为传入的引用的拷贝再次赋值后,它指向了方法体中产生的实例b,如下图:
经过上图,可以看出:在方法体中传入的引用的拷贝指向确实改变了,它指向了新构造的实例b。但是在方法体外面,原来的实例指向还是没变,所以这回的输出还是10。
4.小结
在Java的方法调用中,方法中的参数是以传值的形式进行的,不管它是什么数据类型。如果是基本数据类型,则就是传入该值的一个拷贝;如果是类类型,则传入的是引用的一个拷贝。归根结底还是传的值。
总结:
1.java的基本数据类型是传值调用,对象引用类型是传引用。
2.当传值调用时,改变的是形参的值,并没有改变实参的值,实参的值可以传递给形参,但是,这个传递是单向的,形参不能传递回实参。
3.当引用调用时,如果参数是对象,无论对对象做了何种操作,都不会改变实参对象的引用,但是如果改变了对象的内容,就会改变实参对象的内容。