一道“简单”的面试题_自动装箱和拆箱源码解析

探讨Java面试中关于Swap函数的实现难点,包括值传递、反射修改final字段及自动装箱的影响。揭示如何正确交换两个Integer对象的值。

所有原创文章优先更新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);
    }

可是运行一下,就发现又入坑了。
image.png-30.6kB

因为,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
class Field extends AccessibleObject implements Member
发现它继承自AccessibleObject这个访问权限类,在这个类中,有个setAccessible方法

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);
    }

执行~ 这次没报错,可是运行结果却很奇怪。
image.png-14.1kB

b并不是我们预期的1,怎么回事呢,我们找到我们的class文件,javap看看它的字节码。
image.png-35.8kB
再把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看一下
image.png-28.4kB
Integer a = 1,调用valueof方法,会返回cache下标为1+128=129的数,即1。
同理b=2。
image.png-32.6kB
接着,我们设置a1的value为2,value.set(a1,b1.intValue());也是下标为130的cache值。
image.png-30.8kB
再设置b1的值为a1,
image.png-34.5kB
当当,发现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就行了。
image.png-13kB

或者,我们用Filed类提供的setInt(Object obj,int i)也是可以的。

版权声明:本文由蚂蚁的宝藏创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处,本文作者为蚂蚁的宝藏,本文标题为一道“简单”的面试题_自动装箱和拆箱源码解析,本文链接为http://mayibz.me/2018/02/05/一道“简单”的面试题自动装箱和拆箱源码解析.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值