所有原创文章优先更新http://mayibz.me,欢迎关注。
面试题:
描述: 实现swap(a,b)函数,交换a和b的值。输出:
交换前 a=1 b=2
交换后 a=2 b=1
public static void main(String[] args){
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("交换前 a="+a+" b="+b);
swap(a,b);
System.out.println("交换后 a="+a+" b="+b);
}
}
简单吗? 来看看到底坑有多少?
坑 一
public static void swap(Integer a1,Integer b1){
Integer temp = a1;
a1 = b1;
b1 = temp;
}
这个写法,了解值传递的话,都不难看出,是不会成功的,这个操作只是,让a1的引用指向了b,让b1的引用指向了a。
Integer里面不是用int的value来保存值吗?那么我用setValue(int value)不就好咯?可是,Integer并没有这个方法,怎么办?那干脆用反射来暴力修改这个value。
坑 二
public static void swap(Integer a1,Integer b1) throws NoSuchFieldException, IllegalAccessException {
Field value = Integer.class.getDeclaredField("value");
int temp = a1.intValue();
value.set(a1,b1.intValue());
value.set(b1,temp);
}
可是运行一下,就发现又入坑了。
因为,Integer类中private final int value;
value是private final类型的!也就是说jdk的法律命令禁止修改private属性的字段。我们仔细看看,到底有没有手段来强j这个字段。
public void set(Object obj, int i)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, i);
}
在Filed的set方法中,有一个override的开关,默认是false,就会去检查字段的访问权限。那么怎么关闭它呢?
进入Filed字段 public final
发现它继承自AccessibleObject这个访问权限类,在这个类中,有个setAccessible方法
class Field extends AccessibleObject implements Member
public static void setAccessible(AccessibleObject[] array, boolean flag)
throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
for (int i = 0; i < array.length; i++) {
setAccessible0(array[i], flag);
}
}
private static void setAccessible0(AccessibleObject obj, boolean flag)
throws SecurityException
{
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Cannot make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
}
可以看到,是通过设置这个flag来确定访问权限的。既然如此,那就设置一下咯
坑 三
public static void swap(Integer a1,Integer b1) throws NoSuchFieldException, IllegalAccessException {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
int temp = a1.intValue();
value.set(a1,b1.intValue());
value.set(b1,temp);
}
执行~ 这次没报错,可是运行结果却很奇怪。
b并不是我们预期的1,怎么回事呢,我们找到我们的class文件,javap看看它的字节码。
再把1压栈后,执行了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);
}
如果-128<=i<=127,就直接返回IntegerCache这个缓存中下标为i+128的值,否则的话new一个Integer。
IntegerCache是Integer的一个内部类。维护了256大小的一个Integer数组cache。
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
下标为0的值为-128,依次++。范围是-128~127。
我们在valueof和set那打上断点来debug看一下
Integer a = 1
,调用valueof方法,会返回cache下标为1+128=129的数,即1。
同理b=2。
接着,我们设置a1的value为2,value.set(a1,b1.intValue());
也是下标为130的cache值。
再设置b1的值为a1,
当当,发现129位置的值在我们用反射set的时候变成了2。
正确答案
所以,至此,我们终于明白了结果为什么没有达到我们预想的值。那么这道题该怎么改呢?
public static void swap(Integer a1,Integer b1) throws NoSuchFieldException, IllegalAccessException {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
int temp = a1.intValue();
value.set(a1,b1.intValue());
value.set(b1,new Integer(temp));
}
没错,只要new个Integer就行了。
或者,我们用Filed类提供的setInt(Object obj,int i)也是可以的。
版权声明:本文由蚂蚁的宝藏创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处,本文作者为蚂蚁的宝藏,本文标题为一道“简单”的面试题_自动装箱和拆箱源码解析,本文链接为http://mayibz.me/2018/02/05/一道“简单”的面试题自动装箱和拆箱源码解析.