一道面试题的拓展

来源:互联网 发布:java自学视频百度云盘 编辑:程序博客网 时间:2024/05/22 11:56

偶尔在网上看到一道面试题目:

public class IntegerTest{    public static void main(String[] args) throws Exception {        Integer a = 1;        Integer b = 2;        int c = 3;        int d = 4;        changeIntegerValue(a,b);        changeIntValue(c,d);        System.out.println(a+","+b);        System.out.println(c+","+d);    }        public static void changeIntValue(int c , int d) {        int tmp = c;        c = d ;         d = tmp;    }    public static void changeIntegerValue(Integer a , Integer b){        Integer tmp = a;        a = b ;         b = tmp;    }}

请问控制台输出的是多少?
1,2和4,3
2,1和3,4
2,1和4,3
1,2和3,4
在这里我们暂时不说明答案,再看看另外一道面试题:

public class Test{    String m ;    public static void main(String[] args) throws Exception {        String n = "hello";        Test test = new Test();        test.m="hello";        setValue(n);        setObjValue(test);        System.out.println(n);        System.out.println(test.m);    }    public static void setValue(String m){        m = "nihao";    }    public static void setObjValue(Test test){        test.m="nihaoma?";    }}

请问输出的结果是什么?
如果你将代码运行起来试一下,你就会发现,结果是
1,2和3,4
hello
nihaoma?
java的参数有实参和形参之分
形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。
形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。
可以看出,java参数传递的类型是值传递
对于基本类型而言,形参接收的就是实参的值
对于引用类型而言,形参接收的是实参的地址的值
可以用几幅图来说明:
第一题的int类型传值过程如下:
这里写图片描述
可以看出,changeIntValue里面接受的是a,b的副本值,所以不论changeIntValue里面的a,b怎么变,都不会影响到main栈帧里面的值
如果传递的是引用类型:
这里写图片描述
在上图中,实线表示交换前的指向。3和4存在堆内存中,在changeIntegerValue中交换了形参c和d的指向(虚线所示),但是实际堆内存中,3和4的存储地址并没有变化。这也是为什么交换形参c和d的指向后,原始的实际参数指向并没有变化。所以输出值不会交换
这里值得注意的是,实参和形参的值传递是单向的,即只能从实参传到形参,而不能反向传递。

再来分析第二道题目:
有了上面的基础可以知道,在setValue方法中,只是改变了形式参数指向的内存地址(将形参m指向了”nihao”),原始的的实参仍然指向”hello”,所以并没有改变。
当我们把 Test对象传递给setObjValue方法时:
这里写图片描述
(虚线是改变后的指向)
可以看出,在经过setObjValue后m的指向了”nihaoma”,所以,在main中test.m也会发生改变!

实际上,我们能不能通过这种形式参数来改变实参指向的值呢,答案是可以的,他的原理是直接修改形参指向的那块内存的值,而不是改变他的地址指向,这里就需要用到反射!
看下面代码:

public class Test{    public static void main(String[] args) throws Exception {        Integer a=1 , b = 2;        changeValue(a,b);        System.out.println("a="+a+",b="+b);    }    public static void changeValue(Integer a , Integer b) throws Exception{        int tmp = a;  //1        Field field  = Integer.class.getDeclaredField("value");        //Integer中的value是private,需要设置override=true        field.setAccessible(true);        field.set(a, b);  //2        field.set(b, tmp);//3    }}

输出结果:
a=2,b=2
这里又产生了一个为问题,a的值确实变成了2,但是b的值为什么没有变化?
这里需要涉及到Integer的装箱和拆箱机制
将Integer赋值给int ==>拆箱 调用 IntegerObj.intValue();
将int赋值给Integer==>装箱 调用 Integer.valueOf(i);
在上述的changeValue方法中,第一步进行了拆箱操作t=a.intValue();
经过第二步以后 a的值发生了改变 ==>变成了2
直到第三部:
我们先来看看Integer的set方法:

    public void set(Object obj, Object value)        throws IllegalArgumentException, IllegalAccessException    {        if (!override) {            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {                Class<?> caller = Reflection.getCallerClass();                checkAccess(caller, clazz, obj, modifiers);            }        }        getFieldAccessor(obj).set(obj, value);    }

注意这个方法的参数类型都是Object:
所以在第三步的时候,tmp又会转化成Integer类型
tmp = Integer.valueOf(tmp);
这导致了tmp指向了a,而此时a的值变成了2
所以..最后输出了2,2
我们将方法改变一下:

    public static void changeValue(Integer a , Integer b) throws Exception{        int tmp = a;        Field field  = Integer.class.getDeclaredField("value");        field.setAccessible(true);        field.set(a, b);        System.out.println(tmp);        Integer m = Integer.valueOf(tmp);        System.out.println(m);        field.set(b, tmp);    }

输出结果:
1
2
a=2,b=2
这也对上述结论进行了验证。
要改变这种尴尬有两种解决办法:
一:新建一块内存给tmp
将方法的第一行变成:

Integer tmp = new Integer(a);

这样可以得到正确结果
二:取消二次装箱
将方法的第三行改成:

field.setInt(b, tmp);

java的setInt方法:

public void setInt(Object obj, int i)

因为第二个参数是int型,避免的装箱。

Integer类里面还有很多坑…..比如初始缓存(-128-127)等..就不列举了..

1 0
原创粉丝点击