Java中参数传递到底是传值还是传引用?

首先,笔者可以说:Java不存在传递引用的情况,Java只有一种传递方法就是传值。

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

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

二、Java中引用与C++引用的区别

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

看下面代码:

示例一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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,这就是引用传递。即:它没有任何值的copy,即使是一个地 址,只是另外一个名字而已。

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

但是java里面没有这样的概念,所有的地址传递其行为是值的传递方式,语义上统一成值传递更为清晰,我们只需要考虑这个值具体是什么,无非两种,要么是基本类型值,要么是个地址。
所以我认为这个“引用”的概念放到java中并不合适。只有值传递的说法更合理。

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

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

示例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test2{
    public static void main(String args[]){
        Person p1=new Person(15);
        Person p2=new Person(16);
        System.out.println(p1.age);
        System.out.println(p2.age);
        function(p1,p2);
        System.out.println(p1.age);
        System.out.println(p2.age);
    }
    private static void function(Person p1,Person p2){
        int age;
        age=p1.age;
        p1.age=p2.age;
        p2.age=age;
    }
}

class Person{
    int age;
    public Person(int age){
        this.age = age;
    }
}

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

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

示例三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 public class Test3{
     public static void main(String args[]){
         Person p1=new Person(15);
         Person p2=new Person(16);
         System.out.println(p1.age);
         System.out.println(p2.age);
         change(p1,p2);
         System.out.println(p1.age);
         System.out.println(p2.age);
     }
     private static void change(Person p1, Person p2){
         Person temp;
         temp=p1;
         p1=p2;
         p2=temp;
     }
 }
 
 class Person{
     int age;
     public Person(int age){
         this.age = age;
     }
 }

执行上面的代码,调用change()前后程序输出的都是15、16,此程序试图通过调用change()交换p1和p2,但是没有成功,为什么呢?因为p1和p2是值传递,change()中的p1和p2是main()函数中p1和p2的副本,调用完change()不会对main()中的变量产生影响。再看示例二中,change()函数内改变的并不是p1和p2本身的值,而是p1和p2指向的对象的值,调用完change()后p1和p2仍然指向函数调用前的堆地址,即函数参数是栈中的p1和p2,而不是堆中p1和p2指向的对象,即使你在函数中改变了堆中的对象,但没有改变函数参数的值。所以示例二并不是什么引用传递;可见java中只有值传递。

 

除非注明,饮水思源博客文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.alonemonkey.com/pass-by-value.html

本文链接:http://www.alonemonkey.com/pass-by-value.html