Java中的形参和实参的区别以及传值调用和传引用调用

1.名词解析:

1.形参:用来接收调用该方法时传递的参数。只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间。因此仅仅在方法内有效。

2.实参:传递给被调用方法的值,预先创建并赋予确定值。

3.传值调用:传值调用中传递的参数为基本数据类型,参数视为形参。

4.传引用调用:传引用调用中,如果传递的参数是引用数据类型,参数视为实参。在调用的过程中,将实参的地址传递给了形参,形参上的改变都发生在实参上。
案例分析:
1】.基础数据类型(传值调用)
传值,方法不会改变实参的值。
Java中的形参和实参的区别以及传值调用和传引用调用
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。

Java中的形参和实参的区别以及传值调用和传引用调用

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:

Java中的形参和实参的区别以及传值调用和传引用调用
调用方法test1()时(如下图),a’为引用a的一个拷贝,它也指向当前的实例:

Java中的形参和实参的区别以及传值调用和传引用调用
当在test1()的方法体中修改数据域的值时(如下图):
Java中的形参和实参的区别以及传值调用和传引用调用

因为在方法体中,传入的引用的拷贝指向没有发生改变,所以它修改了数据域的值后会影响到原来的实例的值。

3.2.1 对于第二个测试用例:

同样,构造实例并为其赋值后,再调用方法test2()。在方法体中传入的依然是引用的一个拷贝。如下图:

Java中的形参和实参的区别以及传值调用和传引用调用
在方法test2()中构造一个新的实例并为其赋值100后,如下图:
Java中的形参和实参的区别以及传值调用和传引用调用

在方法体中为传入的引用的拷贝再次赋值后,它指向了方法体中产生的实例b,如下图:
Java中的形参和实参的区别以及传值调用和传引用调用

经过上图,可以看出:在方法体中传入的引用的拷贝指向确实改变了,它指向了新构造的实例b。但是在方法体外面,原来的实例指向还是没变,所以这回的输出还是10。

4.小结

在Java的方法调用中,方法中的参数是以传值的形式进行的,不管它是什么数据类型。如果是基本数据类型,则就是传入该值的一个拷贝;如果是类类型,则传入的是引用的一个拷贝。归根结底还是传的值。

总结:

1.java的基本数据类型是传值调用,对象引用类型是传引用。

2.当传值调用时,改变的是形参的值,并没有改变实参的值,实参的值可以传递给形参,但是,这个传递是单向的,形参不能传递回实参。

3.当引用调用时,如果参数是对象,无论对对象做了何种操作,都不会改变实参对象的引用,但是如果改变了对象的内容,就会改变实参对象的内容。