1,思考以下程序的输出结果
@Test public void test1() throws Exception { Integer num1 = 1; Integer num2 = 2; System.out.println(num1+" : "+num2); sweap(num1,num2); System.out.println(num1+" : "+num2); } private void sweap(Integer num1, Integer num2) { Integer temp = num1; num1 = num2; num2 = temp; }
注:引用传递传递的是地址,准确的说是地址的文本,程序在调用sweap函数时,将num1的地址传递给sweap函数内的num1,但是这两个num1,并不是同一个变量,只是指向了同一个地址,所以说,当num1的引用指向num2的地址的时候,对主函数中的num1是没有影响的。
@Test public void test2() throws Exception { User user1 = new User("user1"); User user2 = new User("user2"); System.out.println("*******处理前*******"); System.out.println(user1); System.out.println(user2); System.out.println("*******sweap*******"); sweap(user1, user2); System.out.println(user1); System.out.println(user2); System.out.println("*******handle******"); handle(user1, user2); System.out.println(user1); System.out.println(user2); System.out.println("*******toNull******"); toNull(user1, user2); System.out.println(user1); System.out.println(user2); } private void sweap(Object num1, Object num2) { Object temp = num1; num1 = num2; num2 = temp; } private void handle(User user1,User user2) { String name1 = user1.getName(); user1.setName(user2.getName()); user2.setName(name1); } private void toNull(User user1,User user2) { user1 = null; user2 = null; }
注:
(1)这里的sweap函数的原理跟上面的一样,只修改引用的指向,对主函数中的参数并无影响
(2)handle函数中同样,主函数的user1将地址传递给函数中的user1,他们两个虽然不是同一个变量但是确指向了同一个对象,在handle函数中,可以通过getName()获取到对象的name,也可以通过setName()给name重新赋值。
(3)toNull函数中,将参数置空其实是将toNull中的user1变量的引用与主函数中用户对象断开联系,也不会影响到对象的值。
2,交换两个Integer对象的值的正确操作:
private void sweap1(Integer num1, Integer num2) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = Integer.class.getDeclaredField("value"); field.setAccessible(true); Integer temp = new Integer(num1.intValue()); field.set(num1,num2.intValue()); field.set(num2, temp); }
3,涉及到的知识点
(1)通过反射去修改final类型的值
在Integer的源码中可以看到,Integer的value是private,final的,所以直接访问就会报错,如下:
private void sweap1(Integer num1, Integer num2) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = Integer.class.getDeclaredField("value"); Integer temp = new Integer(num1.intValue()); field.set(num1,num2.intValue()); field.set(num2, temp); }
@Test public void test1() throws Exception { try { Integer num1 = 1; Integer num2 = 2; System.out.println(num1+" : "+num2); sweap1(num1,num2); System.out.println(num1+" : "+num2); } catch (Exception e) { e.printStackTrace(); } }
解决方法:使用field.setAccessible(true);可以绕过安全检查。
在Integer的源码中可以看到,程序先判断
可以看出通过field.setAccessible(true);设置了override的值;
在来看field.set()方法的源码
由上可以看,程序会在进行安全检查之前先判断override的值,在override的值设置为true后,就不再进行安全检查。
正确的交换写法:
private void sweap1(Integer num1, Integer num2) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = Integer.class.getDeclaredField("value"); field.setAccessible(true); Integer temp = new Integer(num1.intValue()); field.set(num1,num2.intValue()); field.set(num2, temp); }
(2)注意temp的类型不可以是int
@Test public void test1() throws Exception { try { Integer num1 = 1; Integer num2 = 2; System.out.println(num1+" : "+num2); sweap1(num1,num2); System.out.println(num1+" : "+num2); } catch (Exception e) { e.printStackTrace(); } } private void sweap1(Integer num1, Integer num2) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = Integer.class.getDeclaredField("value"); field.setAccessible(true); int temp = num1.intValue(); field.set(num1,num2.intValue()); field.set(num2, temp); }
这是涉及到Java中装箱,拆箱的原理:
在Field.set()方法中,第二个参数指定的数据类型是Object,所以当传递一个int对象的时候,jdk就会对其做自动装箱,将其封装成Integer对象
field.set(num1,num2.intValue());
的完成操作应该是:
num1.valueOf(num2.intValue());
所以改该行代码执行完之后,num1的value的值已经变成了2;
而temp是指向num1的value的,所以他的值也就是2;
因此field.set(num2, temp);并不能将num2的value设置成num1最开始的值。
那为什么该城Integer temp = new Integer(num1.intValue())就可以了呢?
因为这样写是给temp创建了一个新的对象,而不是指向num1值的一个引用,所以num1的变化,并不会引起temp的变化。
扩展:包装类
(1)在jdk1.5版本之前:
Integer num = new Integer(3);
在jdk1.5版本,为简化操作:
Integer num = 3;
这种写法只是为了简化编码,但是实际的操作依然是new Integer(3),只不过在这里做了基本数据类型的自动装箱
num = num + 1;
这里的num是个Integer类型的对象,这计算的时候会进行自动拆箱,转换成基本数据类型;实际操作时
num = num.intValue()+1;
这里需要注意的是Integer类型的num是可以为null的
所以在num的值为null时对其进行运算,在自动拆箱调用其intValue()方法时就会抛出空指针异常
(2)Integer num1 = 127;
Integer num2 = 127;
syso(num1 == num2)
这里的输出结果会是true,因为Integer中缓存了-128~127,这些数值在获取的时候是拿的同一个对象
Integer num3 = 128;
Integer num4 = 128;
syso(num3 == num4)
这里输出就会是false,128并不在缓存的范围内,通过看Integer源码也能看出来,在byte范围之外会new 一个新的对象。