最近看到一道挺有意思的面试题,考察了好几个Java基础知识点,题目如下:
/*** @author 风济海* @description 交换两个Integer的值*/public class SwapDemo {public static void main(String[] args) {Integer a = 1,b = 2;System.out.println("交换前:a="+a+", b="+b);swap(a,b);System.out.println("交换后:a="+a+", b="+b);}*** 交换两个Integer的值(实现该方法)*/private static void swap(Integer a1, Integer b1) {}}
下面根据“解法”逐个分析这道题所考察的知识点。
01
—
方案1:中间变量
看到题目中swap方法参数类型是引用类型,如果对Integer底层不够了解,可能首先想到的实现方式,是通过中间变量来实现,具体如下:
private static void swap(Integer a1, Integer b1) {Integer tmp = a1;a1 = b1;b1 = tmp;}
运行main方法的结果如下:
交换前:a=1, b=2交换后:a=1, b=2
从运行结果看,显然这种实现方式是错误的,原因是什么呢?
虽然Integer是引用类型,但是Integer是使用final类型的int进行数据存储,final类型的变量不能够被重新赋值。
private final int value;public Integer(int value) {this.value = value;}
所以swap方法参数接收到的实际上是变量a、b的副本。
02
—
方案2:反射方式
本质上Integer还是对象,我们一般操作对象的变更是通过set方法,但是从上面介绍Integer中value属性是final类型,所以只能通过反射(第二个基础知识点)的方式来实现。
private static void swap2(Integer a1, Integer b1) throws NoSuchFieldException, IllegalAccessException {Field field = Integer.class.getDeclaredField("value");对所有属性设置访问权限,包括私有成员变量,以及被final修饰的属性field.setAccessible(true);int tmp = a1.intValue();field.set(a1,b1);field.set(b1,tmp);}
运行结果:
交换前:a=1, b=2交换后:a=2, b=2
发现a的值变了,但是b却没变,是啥原因呢?往下看。
03
—
方案3:反射方式优化
b没变的原因涉及到了Java中的自动装箱,a的类型是Integer,而1的类型是int,会自动装箱,可通过javap -c SwapDemo.class查看字节码:
......public static void main(java.lang.String[]);Code:0: iconst_11: invokestatic #2 Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: astore_15: iconst_26: invokestatic #2 Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;9: astore_210: getstatic #3 Field java/lang/System.out:Ljava/io/PrintStream;......
从字节码可以看出,在装箱的时候自动调用的是Integer的valueOf()方法(拆箱调用的是intValue()方法),查看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类,它的部分源码:
private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {high value may be configured by propertyint h = 127;......high = h;......}private IntegerCache() {}}
从上面两段代码可以看出来,通过valueOf()方法创建对象的时候,如果数值在[-128,127]之间,则返回指向IntegerCache.cache[]的引用,否则返回的是一个新创建的Integer对象。
延伸:IntegerCache这么设计的好处,1)提高效率,2)减少内存分配
回到题目,swap2方法中的实现结果为啥b还是2,是因为:原本a经过自动装箱所对应数组IntegerCache[]下标为129的元素值为1,当执行完field.set(a1,b1)这行代码时,该下标为129位置的值被改为2,所以,在执行field.set(b1,tmp)这行代码时,先获取tmp的值,经过自动装箱获取到的值是缓存数组中下标为129的已经被改的2,所以执行完field.set(b1,tmp),b的结果还是2。
因此,正确的实现方式也就明确了:避免装箱(其实就是避免走缓存),有两种方式,第一种:
private static void swap3(Integer a1, Integer b1) throws NoSuchFieldException, IllegalAccessException {Field field = Integer.class.getDeclaredField("value");/ 对所有属性设置访问权限,包括私有成员变量,以及被final修饰的属性field.setAccessible(true);// 避免装箱Integer tmp = new Integer(a1);field.set(a1,b1);field.set(b1,tmp);}
第二种,使用setInt()赋值:
private static void swap4(Integer a1, Integer b1) throws NoSuchFieldException, IllegalAccessException {Field field = Integer.class.getDeclaredField("value");// 对所有属性设置访问权限,包括私有成员变量,以及被final修饰的属性field.setAccessible(true);int tmp = a1;field.setInt(a1,b1);field.setInt(b1,tmp);}
这两种实现方式执行结果:
交换前:a=1, b=2交换后:a=2, b=1
04
—
考察知识点总结
涉及知识点:
2、包装类型缓存
3、自动装箱拆箱
4、Java反射机制
这道题目的是用来考察面试者对这几个知识点的掌握程度,但是需要特别注意的是:上面的正确的实现方式会更改IntegerCache缓存中的值,对正常的使用有影响,不建议在实际项目中这样做。
如果你还有别的实现方式,欢迎留言交流~
-END-
如果觉得本文对你有用
请长按二维码,关注 风济海,顺便点个 在看 吧

转发至 朋友圈,是对我最大的支持




