值传递 / 引用传递
值传递:就是在方法调用的时候,实参是将自己的一份拷贝赋给形参,在方法内,对该参数值的修改不影响原来的实参。
引用传递:是在方法调用的时候,实参将自己的地址传递给形参,此时方法内对该参数值的改变,就是对该实参的实际操作。
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 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
如果我们这么写:
public class test{
public static void main(String[] args) {
int A = 2;
int B = 3;
swap(A, B);
System.out.println(A);
System.out.println(B);
}
public static void swap(int a, int b){
int tmp = a;
a = b;
b = tmp;
}
}
运行结果为:
2
3
发现A和B的值并没有交换,为什么呢?
因为Java中采用的是值传递,也就是说执行swap(int a, int b)时,这里的参数a和b,只是A和B的副本,函数的运行结果并没有改变原来A和B的值。
那么采用Integer呢?
如果将上面的int类型转变为Integer,swap(Integer a, Integer b)会不会实现交换功能呢?
public class test{
public static void main(String[] args) {
Integer A = 2;
Integer B = 3;
swap(A, B);
System.out.println(A);
System.out.println(B);
}
public static void swap(Integer a, Integer b){
Integer tmp = a;
a = b;
b = tmp;
}
}
运行结果为:
2
3
可见还是没有完成交换!
去查看Integer的源码:
public final class Integer extends Number implements Comparable<Integer> {}
可以看到Integer使用final修饰的int进行存储。final修饰的变量不能被重新赋值,所以操作参数传递变量时,实际上是操作变量对象的副本(Java中的包装类型都是默认使用这种方式实现的,使用拷贝副本的方式提升效率和减少内存消耗)。
如果换作是数组呢?
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
运行结果为:
1
0
这里方法array是对象的引用arr的拷贝,而不是对象本身的拷贝,因此, array 和 arr 指向的是同一个数组对象。
如果换做是一般对象呢?
很多程序设计语言(特别是 C++ 和 Pascal)提供了两种参数传递的方式:值调用和引用调用。
有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
class User {
private String name;public User(String name) {
this.name = name;
}public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}@Override
public String toString() {
return name;
}
}
测试:
public class test{
public static void main(String[] args) {
User A = new User("ali");
User B = new User("bd");
System.out.println("交换前name:" + A + "-->" + B);
swap(a,b);
System.out.println("交换后name:" + A + "-->" + B);
}private static void swap(User a, User b) {
User tmp = a;
a = b;
b = tmp;
}
}
运行结果为:
交换前name:ali-->bd
交换后name:ali-->bd
发现还是没有交换!
所以到底有没有交换,主要是看它修改的是变量(引用)还是修改的堆里面的对象。
交换前:
交换后:
通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 A 和 B 中的对象引用。swap() 方法的参数 a 和 b 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。
那么到底该如何实现交换两个变量的值呢?
用容器(或者数组)
例如:
public class test{
public static void main(String[] args) {
int[] arr = {2, 3};
int A = arr[0];
int B = arr[1];
swap(arr, 0, 1);
A = arr[0];
B = arr[1];
System.out.println(A);
System.out.println(B);
}public static void swap(int[] arr, int a, int b){
int tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
}
运行结果为:
3
2
用反射
public static void swap(Integer a, Integer b) throws Exception {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true); //设置可以访问成员的私有不可变的变量
Integer tmp =new Integer(a.intValue());
field.set(a, b.intValue());
field.set(b, tmp);
}