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 一个新的对象。

 

posted on 2020-03-13 09:50  song.yan  阅读(246)  评论(0编辑  收藏  举报