[Java] 由swap方法引发的重思考

来源:互联网 发布:软件游戏制作学校 编辑:程序博客网 时间:2024/05/29 09:17

老生常谈的话题:Java 传参的方式到底是传值 还是传引用?

其实不同的角度理解都有道理,而接下来 先摆明我的立场:传值!


首先,来分析下Java中 "=" 的意义。

在"="的右边,是一个常量、表达式、或者对象(统统可以看成对象),在内存中会为它们分配空间。

而"="的左边,是一个引用(我们常说变量),保存的是右边对象的地址。

"="的实际意义就是 将右边对象的地址传给左边的引用,使得该引用指向了右边对象。


所以,Java里的引用可以看成是(C/C++所说的指针)。


另外,要明确一点:Java中都是通过使用引用来操作对象的。

为什么这么说呢?因为系统为"="右边的对象分配了空间,但是程序无法直接获取它,必须通过定义引用来得到。然后后面对该对象的操作都是通过操作该引用来实现。不管对于基本数据类型还是类对象,都是一样的处理流程。下面对基本数据类型和复杂的类对象进行说明:

(1)Java中创建基本数据类型变量的流程

int a = 3;int b = 3;
编译器会首先创建一个变量名为a 的引用,然后在代码所在栈中查找是否有3这个常量值(如果没有,就在栈上开辟空间放入3),让a指向3。

接着编译器再创建一个变量名为b 的引用,在栈中发现已有3这个值,就让b也指向3。

这样,虽然a和b是不同的引用,但是都指向同一个值,a和b都是对这个值的引用。

(2)Java中创建复杂类型对象的流程

Person person1 = new Person("cb");Person person2 = new Person("adm");
同样,编译器会首先在栈中创建一个对象变量名为person1的引用,然后在堆中实例化一个对象,让person1指向它。
接着,编译器再创建一个对象变量名为person2的引用,然后在堆中又实例化一个对象,让person2指向它。
当然,person1和person2是不同的对象变量,而且也指向不同的对象。

这些对象都是无法直接访问,都是通过定义了引用去指向它们。
------------------------------------------------------------------------------------------

预热结束了,开始直奔主题:为什么说Java传参都是传值呢?

因为Java中传递参数时,获得的形参其实是被操作对象的引用的一份副本(即形参是实参的拷贝)当然这份副本也是指向被操作对象的。从这种角度来说,Java的传参,就是传值的方式(传副本)。
下面还是通过基本数据类型和复杂类对象进行说明。

(1)基本数据类型  传参分析

public void badSwap(int var1, int var2){    int temp = var1;    var1 = var2;    var2 = temp;}//调用badSwap方法int main(...) {    int a = 3, b = 5;    badSwap(a, b);}

大家都知道这个方法是无效的。

main()方法中变量a是对常量3的引用,变量b是对常量5的引用。当a/b作为实参传递给badSwap方法时,传递的是a/b的拷贝(a'/b')。此时,a与a'指向3,b与b'指向5。经过badSwap方法处理后,改变的只是a'/b'的指向,并未影响到a/b。如图。


如果我们把传入的int型变量改为Object型结果也是一样的。改变的只是这份副本而已。就不分析了,下面分析复杂类对象的另一种情况。

(2)复杂类型的对象 传参分析

public void changeName(Person person){    person.name = "adm";}public static void main(String [] args){    Person person = new Person("cb","Shanghai");    System.out.println("Name: " + person.name + ", Address: " +person.address);    changeName(person);    System.out.println("Name: " + person.name + ", Address: " +person.address);}

执行main方法,将得到以下输出:

Name: cb, Address: ShanghaiName: adm, Address: Shanghai

结果:changeName方法成功改变了person的name值。为什么呢?


在main()方法中,person仅仅是对象的引用。当向changeName()传递person时,Java仅仅是传递了person这个引用的一个副本,即传向方法的引用实际上是原始引用的副本。这是一个传值操作(即副本值)。这样当Java传递对象引用的副本给方法后,就有两个引用指向了同一对象。


那为什么能成功改变person的值呢,如下图:

因为传值进来的是引用的副本,它也指向了原来的对象,虽然对副本的交换是无效的,但对副本所指向的对象的内部 进行的操作是会起作用的。图解很清晰了,引用所指向的对象的name属性指向了新的内存空间(存放着"adm")。


总结下:
1.Java中,使用引用来操作对象
2.Java中,传参实际上传递的是 副本值(即指向 被操作对象的引用 的副本)。

不错的参考:
http://blog.csdn.net/nameoccupied/article/details/8954367
http://blog.csdn.net/lcore/article/details/8712770


ps:Java中 传参方式 与 C/C++中 是不同的原理,不可混淆。比如参数是Object类型时,Java中对内部成员变量的修改有效;C/C++中则无效。因为C/C++中如果不通过指针/引用 传递参数(即不通过传址方式),就会创建完全崭新的对象。