美文网首页
java 方法中交换Integer类型的局部变量a,b

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

作者: 若尘0328 | 来源:发表于2017-11-02 20:05 被阅读301次

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

        //要求写一个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);
        }
    

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

    • Integer的自动装箱
    • java的值传递和引用传递
    • 对Integer类的源码分析
    • java的反射机制
    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);
        }
    

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

    相关文章

      网友评论

          本文标题:java 方法中交换Integer类型的局部变量a,b

          本文链接:https://www.haomeiwen.com/subject/vwsopxtx.html