Java交换两个Integer-一道无聊的题的思考

1.最近网上看到的一道题,有人说一道很无聊的题,但我觉得有必要记录一下。

2.题目

 

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

    public static void swap(Integer a,Integer b) throws Exception {
        //TODO 请实现逻辑
    }

  看了题目之后,首先想到的是 加减法,异或操作交换等。但仔细思考之后,发现考察点并不是这个。至少,你先要了解java的引用和值传递的知识。

3. Java中都是值传递

也就是说,函数的参数变量都是对原来值的copy,这也是java和c的一个明显区别。举个例子。

1处和2处两个引用的指向都是同一块内存,但是count == countCopy答案是false。

你在家看电视,用遥控器正在更换频道,这时候你爸跟你说“把遥控器给我!刚才那个节目很好看”。此时,你为了不丢失对电视的控制权,你从抽屉里拿了一个新的遥控器给了你爸(复制一个新的)。新、旧两个遥控器就如同上面的count,countCopy。

    public static void main(String[] args) throws Exception {
        
        Integer count = new Integer(100);//1
        test(count);
    }
    
    public static void test(Integer countCopy){//2
        System.out.println(countCopy);
    }

 

4.回到题目

如果给出的不是引用类型Integer而是int交换,这题是无解的。因为swap函数里的a,b都是引用的copy。所以你改变swap中a,b的引用指向是没用的,因为无法影响到主函数中的引用a,b的指向。所以思路还是只能从更改引用指向的真实内存值来解决(要拆开电视,更换零件;只拿着遥控器一吨操作是没法让电视机硬件产生变化的),所以自然要用到反射了。最初的我解答如下(下面这份代码是有问题的)

public static void swap(Integer a,Integer b) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        int tmpA = a.intValue();//3
        int tmpB = b.intValue();//5
        valueField.set(a,tmpB);
        valueField.set(b,tmpA);
    }
//程序输出结果

before swap: a=3,b=5
3========>5
5========>5
after swap: a=5,b=5

发生了什么?为什么交换后b=5而不是3?别急我们根据上面的代码,进行DEBUG。

这里要补充一个细节,你可以在valueOf函数里面打个断点,发现的确会进去。

Integer a = 3;
//等价与
Integer a = Integer.valueOf(3);

那么上面有问题的代码  valueField.set(b,tmpA); 因为tmpA是int类型,在赋值的时候也会隐式调用Integer.valueOf封装成对象,然后再进行set赋值。怀疑问题就是在set这个方法了吗?但是 valueField.set(a,tmpB);是有效的,valueField.set(b,tmpA)是无效的。稍微改动一下程序,进一步探索。

public static void swap(Integer a,Integer b) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        int tmpA = a.intValue();//3
        int tmpB = b.intValue();//5
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
        valueField.set(a,tmpB);
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
        valueField.set(b,tmpA);
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
}

程序输出

before swap: a=3,b=5
3======5
5======5
5======5
after swap: a=5,b=5

可以发现在第一个进行反射赋值valueField.set(a,tmpB);后,Integer.valueOf(3) 等于 5 ???

进去看看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是个什么鬼?而且IntegerCache.low = -128, IntegerCache.high = 127。valueOf(3)肯定会命中缓存,那么通过Debug调试,发现IntegerCache的确出错了,cache[3] = 5 (其实真实3的缓存下标并不是3,而是i + (-IntegerCache.low),这里便于说明理解)。

经过这些分析,问题表现在 valueField.set(a,tmpB); 赋值后 

命中IntegerCache,获取cache(5)即5,并更新缓存cache(3)=5

那么如果解决呢,其实只要避开调用valueOf即可,也就是通过new Integer()来绕开缓存。修改后的代码如下:

public static void swap(Integer a,Integer b) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        int tmpA = a.intValue();//3
        int tmpB = b.intValue();//5
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
        valueField.set(a,new Integer(tmpB));
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
        valueField.set(b,new Integer(tmpA));
        System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
}


//输出

before swap: a=3,b=5
3======5
5======5
5======3
after swap: a=5,b=3

但是 Integer.valueOf(3)的值还是5,如果程序的其他地方也用到了Integer.value(3)那么将造成致命bug。所以说尽量不要用反射去改变类的私有变量。

 

转载于:https://my.oschina.net/eqshen/blog/3036658

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值