提到交换两个数a, b的值,一定可以想起很多方法,a = a+b;b = a - b; a = a - b;借助temp的方法等等,但是如果食用swap方法的话,这个问题可是很有研究的了,要求使用swap(Integer a, Integer b)签名,交换a,b的值
int a = 1;
int b = 2;
//before
System.out.println(“a = %s, b = %s”, a, b);
swap(a, b);
System.out.println(“a = %s, b = %s”, a, b);
由于java中对于非原生类型都说传递引用,而对于原生数据类型都说通过传递值的方式传递参数的,如果仅仅通过传递值的方式来交换a,b的值,在Java中是不可能实现的,所以在定义函数签名的时候使用了Integer的方式,通过传递引用。
下面我们就来一步步的揭开这个题目的面纱:
通过传统方法,使用临时变量的方式
public void swap(Integer a, Integer b) {
Integer temp = a;
a = b;
b = temp;
}
以上方法看着没有任何问题,借助一个临时变量来实现变量的交换,那么运行一下,结果并没有想象的那样,等于没有任何的交换,那这个是为什么呢,明明传递的是引用,而且交换的是引用?
首先我们理解下由int -> Integer这个过程,我们知道这个叫做自动装箱,是JDK自带的功能,那么这个自动装箱经历了什么?我们可以通过javap -verbose命令查看编译后的class文件,我们会发现,在自动装箱的过程中,并不少直接new Integer,而是通过Integer.valueOf()方法将数字转换成Integer
34: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
37: iload_2
38: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
41: invokestatic #8 // Method swap:(Ljava/lang/Integer;Ljava/lang/Integer;)V
那么Integer.valueOf
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
从IntegerCache中取出的,也就是说从缓冲中去的的值,而函数中的赋值操作,等于将i1->a 变成i1->b,将i2->b变成i1->a,这个和a,b本身没有任何关系,所以并没有成功交换两个数的值
借助反射来实现
那么这样直接赋值的方式无法改变两个数的值,那么我们可以猜想到,我们可以使用反射,而在Integer中存储值的是
private final int value;
如果通过反射的方式改变这个引用的值,应是可用行的,那么我们就尝试这种方法
public static void swap(Integer i1, Integer i2) throws NoSuchFieldException, IllegalAccessException {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int temp = i1.intValue();
field.set(i1, i2.intValue());
field.set(i2, temp);
}
然后不幸的是,我们居然得到这样离奇的结果
before: a = 1, b = 2
after: a = 2, b = 2
这个是为什么呢,首先我们将i1的值赋值给了temp = 1,再将i2.intValue()的值赋值给了i1 = 2;,也就是说我吗将2赋值给了Integer.value(1)的这个实例,然后再将temp = 1赋值给i2,这里注意set函数的签名
public void set(Object obj, Object value)
第二个参数接受的是一个对象,而我们传递的是temp,那么就要进行自动装箱成Integer.value(1),而field.set(i1, i2.intValue());步,已经将Integer.valueOf(1)的值赋值为2,所有在field.set(i2, temp);就是将2赋值给了i2,就有了上面的输出结果。
再次借助反射
反射还是不行,主要是引用将temp自动装箱成了Integer.valueOf(1),而这个和i1使用了同一个引用,而直接使用了i1的值,那么如果不让temp自动装箱成Integer.valueOf(1)是不是就解决问题了,于是有了第三种方法:
public static void swap(Integer i1, Integer i2) throws NoSuchFieldException, IllegalAccessException {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
Integer temp = new Integer(i1.intValue());
field.set(i1, i2.intValue());
field.set(i2, temp);
}
这时我们得到一个正确的结果输出:
before: a = 1, b = 2
after: a = 2, b = 1
大功告成,终于将两个整数的值进行的交换
这时别高兴的太早,上面提到了一个问题,我们将Integer.value(1)中的值赋值成了2,也就是说Integer.valueOf(1) == 2将返回true,这样对整个系统都造成了影响,这样的方法也是有缺陷的,使用时要谨慎
通过Integer的setInt代替set方法
我们还有一种方式,可以避免自动装箱,Integer有一个setInt方法,签名为
public void setInt(Object obj, int i)
由于第二个参数是整型,也就避免了自动装箱,这时的实现代码为
public static void swap(Integer i1, Integer i2) throws NoSuchFieldException, IllegalAccessException {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int temp = i1.intValue();
field.setInt(i1, i2.intValue());
field.setInt(i2, temp);
}
同样这个方法也有上述方法3种的缺陷