一道java JDK面试题引发的思考

这道面试题所设计的知识点:

  • 传值和传引用的区别
  • 装箱和拆箱
  • java的内存模型
  • 反射

面试题需求:

主方法定义两个Integer变量,并赋值,然后通过一个swap()方法交换变量的值,请写出swap()中的实现

public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("before swap :a = " + a + ",b = " + b);
swap(a, b);
System.out.println("after swap :a = " + a + ",b = " + b);
}

当看到这个面试题的时候第一感觉就是在swap()方法中定义一个变量,例如:

private static void swap(Integer num1, Integer num2) {

Integer tmp = num1;
num1 = num2;
num2 = tmp;

}

但是运行结果不尽人意:

before swap :a = 1,b = 2
after swap :a = 1,b = 2

这就牵扯到传值的方式,传值的方式有两种,按值传递,引用传递,那上面这个swap()方法是按那种方式传值的呢?

这里是按值传递;

值传递:
在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

image.png

到了swap()方法,还没有进行交换时:

image.png

经过了swap()方法之后,num1和num2进行了交换:

image.png

可以看到其实并没有影响到a和b的值;

这里还有一个问题就是Integer a = 1; 是自动装箱,怎么实现自动装箱的? 大家都用过Integer.valueOf()

Integer a = Integer.valueOf(1);所以说Integer a = 1就是一个装箱的操作;

那valueOf()这个方法中又做了什么事呢?

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

有两个变量 low ,high ,low=-128,high=127;也就是说在这两个值之间的值都会从缓存里去取;不在这两个值之间的才会去new 一个Integer对象;

上面我们使用一个中间变量是不能实现的交换的,那我们使用发射试试;

private static void swap(Integer num1, Integer num2) {

try {
int tmp = num1.intValue();
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true); // private final int value; 私有属性,使用暴力反射
field.set(num1, num2);
field.set(num2, tmp);
} catch (Exception e) {
e.printStackTrace();
}
}

运行结果:

before swap :a = 1,b = 2
after swap :a = 2,b = 2

发现还是不行;只是a成功交换,但是b没有交换;这里因为num1的下标对应的值被改成了2,所以当给num2设置值的时候,其实设置的还是2,这里可以打个断点到valueOf()这个方法中看看;这里就不阐述过多;

为了引出正确的解决方法我们引入一个简单的知识点:上面我们也看了在[-128~127]之间是从缓存中取的;

那下面这两段代码是打印true还是false呢?

public static void main(String[] args) {
Integer a = 1;
Integer b = 1;

System.out.println(a==b);
}
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
System.out.println(a==b);
}

1之间的比较是true ,128之间的比较是false,1没有超出这个缓存的范围,但是128已经超出了缓存的范围,就要就new Integer();

所以这个面试题正确的解决方法是:

private static void swap(Integer num1, Integer num2) {
try {
int tmp = num1.intValue();
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true); // private final int value; 私有属性,使用暴力反射
field.set(num1, num2);
field.set(num2,new Integer(tmp));
} catch (Exception e) {
e.printStackTrace();
}
}

打印结果:

before swap :a = 1,b = 2
after swap :a = 2,b = 1

成功的进行了交换;对num1–>Integer tmp = new Integer(num1);不要让它取缓存里的值;缓存中下标对应的值改变也对num2的赋值没有影响;因为num1是重新new 的对象;

知道了上面的原理,我们把a和b的值分别变成777 和999 那会不会成功交换呢?自己可以下去试一试;

还有没有其他的解决方式呢?当然是有的;我们可以把

field.set(num1, num2);
field.set(num2, tmp);
//改成
field.setInt(num1,num2);
field.setInt(num2,tmp);

field.setInt(num1,num2);中num2是需要int类型;这就不是装箱了而是拆箱

至此 这道面试题所涉及的东西就说完了;