值传递 / 引用传递
值传递:就是在方法调用的时候,实参是将自己的一份拷贝赋给形参,在方法内,对该参数值的修改不影响原来的实参。
引用传递:是在方法调用的时候,实参将自己的地址传递给形参,此时方法内对该参数值的改变,就是对该实参的实际操作。
Java中只有值传递
首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
按值调用(call by value):表示方法接收的是调用者提供的值。
按引用调用(call by reference):表示方法接收的是调用者提供的变量地址。
它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,即,方法不能修改传递给它的任何参数变量的内容。
swap( ) 场景
要求写一个函数交换int类型的a和b的值
在 swap() 方法中,a、b 的值进行交换,并不会影响到 A、B。因为,a、b 中的值,只是从 A、B 复制过来的。也就是说,a、b 相当于 A、B 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
如果我们这么写:
运行结果为:
发现A和B的值并没有交换,为什么呢?
因为Java中采用的是值传递,也就是说执行swap(int a, int b)时,这里的参数a和b,只是A和B的副本,函数的运行结果并没有改变原来A和B的值。
那么采用Integer呢?
如果将上面的int类型转变为Integer,swap(Integer a, Integer b)会不会实现交换功能呢?
运行结果为:
可见还是没有完成交换!
去查看Integer的源码:
public final class Integer extends Number implements Comparable<Integer> {}
可以看到Integer使用final修饰的int进行存储。final修饰的变量不能被重新赋值,所以操作参数传递变量时,实际上是操作变量对象的副本(Java中的包装类型都是默认使用这种方式实现的,使用拷贝副本的方式提升效率和减少内存消耗)。
如果换作是数组呢?
运行结果为:
这里方法array是对象的引用arr的拷贝,而不是对象本身的拷贝,因此, array 和 arr 指向的是同一个数组对象。
如果换做是一般对象呢?
很多程序设计语言(特别是 C++ 和 Pascal)提供了两种参数传递的方式:值调用和引用调用。
有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
测试:
运行结果为:
发现还是没有交换!
所以到底有没有交换,主要是看它修改的是变量(引用)还是修改的堆里面的对象。
交换前:
交换后:
通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 A 和 B 中的对象引用。swap() 方法的参数 a 和 b 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。
那么到底该如何实现交换两个变量的值呢?
用容器(或者数组)
例如:
运行结果为:
用反射