java 方法中交换Integer类型的局部变量a,b

2017-11-02  本文已影响301人  若尘0328

  自己的第一篇博客,各位看官多多指教。这里讲的是一道面试题,题目如下:

    //要求写一个swap方法交换ab值输出打印a=2 b=1
    public static void main(String[] args) {
        Integer a=1,b=2;
        swap(a,b);
        System.out.println("a="+a+"   b="+b);
    }

  这道题目所涉及到的知识点包括:

1. Integer的自动装箱

  java的自动装箱就是自动将原始数据类型(byte,short,char,int,long,float,double,boolean)转换成对应的基本数据类型包装类对象(Byte,Short,Character,Integer,Long,Float,Double,Boolean),比如将int的变量转换成Integer对象
这里的Integer a=1 经过自动装箱以后变为
Integer a=Integer.valueOf(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);
    }

  接着查看IntegerCache这个内部类可以发现java虚拟机在我们首次使用Integer的时候会初始化数值在-128-127之间的整数,而且这个区间范围可以在虚拟机启动的时候自己设定。换句话说,数值在-128-127之间的相同整数都会取自相同的内存具体值。下面例子中a和b都是指向相同的内存地址,而x和y指向两块不同的内存地址。

    Integer x=-129;
    Integer y=-129;
    System.out.println(x==y);    //输出false
    Integer a=-128;
    Integer b=-128;
    System.out.println(a==b);    //输出true
    Integer xx=new Integer(1);
    Integer yy=new Integer(1);
    System.out.println(xx==yy);    //输出false

  说到这里必须说一下valueOf(int i)方法最终返回的是new Integer(i);而Integer类的构造函数如下,这里的value是解题的关键,value在Integer类中是一个final的私有成员变量。

    public Integer(int value) {
        this.value = value;
    }

说出正确答案之前还得看看Integer的源码中的toString方法
public String toString() { return toString(value); }
可见其返回的真实值就是value,并且value就是在构造函数中初始化的。在swap方法中我们拿到了Integer的引用,那么我们就可以通过反射来改变对应实例的局部变量值。具体代码如下:

    private static void swap1(Integer aa, Integer bb) {
        try {
            Field aaValue = aa.getClass().getDeclaredField("value");
            aaValue.setAccessible(true);
            aaValue.set(aa,2);
            Field bbValue = bb.getClass().getDeclaredField("value");
            bbValue.setAccessible(true);
            bbValue.set(bb,1);
            System.out.println(Integer.valueOf(1));//输出2
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

但是运行之后我们发现其结果是:

a=2   b=2

  看到这里是不是感觉很不可思议,但是我们距离真相已经很近了。我们疑惑的是为什么b的值没有发生改变,更疑惑的是为什么Integer.valueOf(1)输出的竟然是2。这是因为基本数据类型在-128-127之间的数都会自动装箱并且从缓存中去取,我们反射改变的只是缓存中的1对应的实例的实例变量value值,我们在bbValue.set(bb,1);的时候1会自动装箱去缓存中去找,而这时缓存中的Integer.valueOf(1)已经是2,所以返回的还是2。解决的方法就是不让其去缓存中找,参考代码如下:

    //正解
    private static void swap1(Integer aa, Integer bb) {
        try {
            Field aaValue = aa.getClass().getDeclaredField("value");
            aaValue.setAccessible(true);
            aaValue.set(aa,2);
            Field bbValue = bb.getClass().getDeclaredField("value");
            bbValue.setAccessible(true);
            //这里的1不会自动装箱,当然就不会从缓存中去取值
            bbValue.set(bb,new Integer(1));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  这里还是要多说一句,如果a,b变量的值是-128-127之外的整数的话虽然会自动装箱,但是不会从缓存中去取值,那么bbValue.set(bb,-128-127之外的整数);就可以了,是不用专门new Integer()的,参考代码如下:

    public static void main(String[] args) {
        Integer a=999;
        Integer b=888;
        swap1(a,b);
        System.out.println("a="+a+"   b="+b);
    }
    private static void swap1(Integer aa, Integer bb) {
        try {
            Field aaValue = aa.getClass().getDeclaredField("value");
            aaValue.setAccessible(true);
            aaValue.set(aa,888);
            Field bbValue = bb.getClass().getDeclaredField("value");
            bbValue.setAccessible(true);
            bbValue.set(bb,999);//这里没有new Integer(999)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

输出结果为a=888 b=999

2. 错误答案示例

  对于这道题目的解法有些同学可能会给出下面的答案,明显是错误的,因为它们具体交换的还是aa和bb所指向的内存地址值,而这一块内存地址值所指向的内存空间的内容并没有改变,所以a和b变量是没有变化的(参考图1)。

    private static void swap(Integer aa,Integer bb){
        Integer temp;
        temp=aa;
        bb=temp;
        aa=bb;
    }

  按照我自己的理解,java的值传递和引用传递实际上都是值传递,因为引用传递最终传递的还是引用对象的内存具体值(下图红色线条代表交换后的状态,可见a和b并没有发生变化)


图1.引用传递(红线代表交换后)
3. 题目变种
    //对于这道题是不能用反射的,因为是基本数据类型的值传递
    public static void main(String[] args) {
        int a=1;
        int b=2;
        swap(a,b);
        System.out.println("a="+a+"   b="+b);
    }

投机取巧的做法如下:

    public static void main(String[] args) {
        int a=1;
        int b=2;
        swap(a,b);
        System.out.println("a="+a+"   b="+b);
    }
    private static void swap(Integer aa, Integer bb) {
        System.out.println("a="+2+"   b="+1);
        System.exit(0);
    }

  题目虽小,却包含很多知识点,希望大家能够有所收获,欢迎留言讨论!

上一篇下一篇

猜你喜欢

热点阅读