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+" //代码效果参考:http://hnjlyzjd.com/hw/wz_25338.html
: "+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
//代码效果参考:http://hnjlyzjd.com/xl/wz_25336.html
@Testpublic 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 一个新的对象。