说明:本文的适用对象为java初学者、如果有读者发现文章中有叙述不妥之处,请指正。

今天在论坛上有人提了一个关于java中调用函数时有没有引用传递的问题,可谓是吵的不可开交。有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下。

一、首先来明确一下"值传递"和"引用传递的"区别

值传递:是对所传递参数进行一次副本拷贝,对参数的修改只是对副本的修改,函数调用结束,副本丢弃,原来的变量不变(即实参不变)
引用传递:参数被传递到函数时,不复制副本,而是直接将参数自身传入到函数,函数内对参数的任何改变都将反映到原来的变量上。
二、java中引用的含义

C++和java中都有引用的概念,但在这两种语言中却有完全不同的含义。C++中我们可以用形如"int &b=a”的形式来定义变量a的一个引用b,b只是a的一个别名,b和a在内存中占同一个存储单元,利用引用机制我们可以在调用函数时实现值的双向传递——即引用传递,看下面代码:

示例一

#include <iostream>
using namespace std;
int main()
{
  void swap(int &,int &);
  int i=3,j=5;
  swap(i,j);
  cout<<"i="<<i<<"j="<<j<<endl;
  return 0;
}
 
void swap(int &a,int &b)
{
  int temp;
  temp=a;
  a=b;
  b=temp;
}

执行上面的程序输出的是i=5 j=3,a和b传递给swap()函数的时候,是传递的他们本身的地址,不是他们的拷贝,所以在函数中对他们的改变可以直接影响到实参a和b,这就是引用传递。

java中的引用更像C++中的指针,当我们定义一个对象时(比如Person p=new Person()),定义的对象实例会放到java堆中,而变量p(即引用)会放到java栈中,p指向堆中的Person对象实例。

三、对引用传递的认识误区

为什么有很多人认为java有引用传递呢?一种情况是有人认为调用函数时其参数有可能是引用(如上面的p),所以java有引用传递,这部分人对引用传递根本没有正确的认识;而另一种情况看似有道理,但是仔细分析也是不正确的的,他们往往会用如下的代码来证明他们的观点:

示例二:


view plain copy to clipboard print ?


1. class Demo{  
2.      int a;  
3.      public Demo(int a){  
4.          this.a=a;  
5.      }  
6.  }  
7.  public class TestQuote{  
8.      public static void main(String args[]){  
9.          Demo d1=new Demo(1);  
10.          Demo d2=new Demo(2);  
11.          System.out.println(d1.a);  
12.          System.out.println(d2.a);  
13.          function(d1,d2);  
14.          System.out.println(d1.a);  
15.          System.out.println(d2.a);  
16.      }  
17.      private static void function(Demo d1,Demo d2){  
18.          int a;  
19.          a=d1.a;  
20.          d1.a=d2.a;  
21.          d2.a=a;  
22.      }  
23.  }


class Demo{     int a;     public Demo(int a){         this.a=a;     } } public class TestQuote{     public static void main(String args[]){         Demo d1=new Demo(1);         Demo d2=new Demo(2);         System.out.println(d1.a);         System.out.println(d2.a);         function(d1,d2);         System.out.println(d1.a);         System.out.println(d2.a);     }     private static void function(Demo d1,Demo d2){         int a;         a=d1.a;         d1.a=d2.a;         d2.a=a;     } }

他们的观点如下:执行上面的代码,调用function()函数以前输出的结果是1、2,调用function()函数之后输出的结果会是2、1,可见在函数内对d1和d2的改变反映到了原来的变量上,要不是不会输出2、1的。

这种解释是很迷惑人的,看上去好像很正确,下面的代码会很好的反驳上面的观点:

示例三:


view plain copy to clipboard print ?



    1. class Demo{  
    2.      int a;  
    3.      public Demo(int a){  
    4.          this.a=a;  
    5.      }  
    6.  }  
    7.  public class TestQuote{  
    8.      public static void main(String args[]){  
    9.          Demo d1=new Demo(1);  
    10.          Demo d2=new Demo(2);  
    11.          System.out.println(d1.a);  
    12.          System.out.println(d2.a);  
    13.          function(d1,d2);  
    14.          System.out.println(d1.a);  
    15.          System.out.println(d2.a);  
    16.      }  
    17.      private static void function(Demo d1,Demo d2){  
    18.          Demo temp;  
    19.          temp=d1;  
    20.          d1=d2;  
    21.          d2=temp;  
    22.      }  
    23.  }


     

    class Demo{
          int a;
          public Demo(int a){
              this.a=a;
          }
      }
      public class TestQuote{
          public static void main(String args[]){
              Demo d1=new Demo(1);
              Demo d2=new Demo(2);
              System.out.println(d1.a);
              System.out.println(d2.a);
              function(d1,d2);
              System.out.println(d1.a);
              System.out.println(d2.a);
          }
          private static void function(Demo d1,Demo d2){
              Demo temp;
              temp=d1;
              d1=d2;
              d2=temp;
          }
      }

    执行上面的代码,调用function()前后程序输出的都是1、2,此程序试图通过调用function()交换d1和d2,但是没有成功,为什么呢?因为d1和d2是值传递,function()中的d1和d2是main()函数中d1和d2的副本,(在傳參的時候只是把棧中的對象名所代表的堆中該對象所分配空間的首地址當一個值傳過去,在調用的函數中如果沒有涉及到對對象內部的成員進行操作的,都不會有對對象名所代表的對象空間的首地址指向的堆中的對象具體空間進行操作的操作————這邊可能講的好亂,但是卻是這個問題的精髓所在哇╮(╯▽╰)╭)调用完function()不会对main()中的变量产生影响。再看示例二中,function()函数内改变的并不是d1和d2本身的值,而是d1和d2指向的对象的值,调用完function()后d1和d2仍然指向函数调用前的堆地址,即函数参数是栈中的d1和d2,而不是堆中d1和d2指向的对象,即使你在函数中改变了堆中的对象,但没有改变函数参数的值。所以示例二并不是什么引用传递;可见java中只有值传递。

     

    但是网上有很多针对"java值传递和引用传递进行的区别"的文章,如果读者看到的话一定要清楚,其中所说的引用传递是不正确的,他们所说的引用传递就是示例二中的那种情况。不幸的是,网上有很多文章都把示例二中的那样的传递看做引用传递,如果读者看到知道是什么意思就行了。