看一道关于Integer的面试题,无意中发掘的一种方式。
题目是要求写一个方法,交换两个Integer的值。
看起来十分简单的一道题目,蕴含很多知识哦,不信往下看。
public static void swap(Integer i1, Integer i2){
int tmp = i1;
i1 = i2;
i2 = tmp;
}
这肯定是错误答案, 因为Integer是对象,这个方法执行过后,原始的两个对象内容未发生改变。
然后,有人给出的答案如下:
public static void swap(Integer i1, Integer i2){
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int tmp = i1.intValue();
field.setInt(i1, i2.intValue());
field.setInt(i2, tmp);
}catch (Exception e){
e.printStackTrace();
}
}
通过反射完成的交换,要是看不懂的,也可以运行一下看看
public static void main(String[] args) {
Integer i1 = 1, i2 = 2;
swap(i1, i2);
System.out.println("i1: " + i1);
System.out.println("i2: " + i2 );
}
运行结果如下:
似乎是正确的,很多人看到这里,没发现问题。
什么问题呢?这段自以为高明的代码,写了一堆反射,看起来很厉害,其实问题很大,而且很厉害的破坏了java内部的Integer。不行你在main方法下面加一点代码看看。
比如:
public static void main(String[] args) {
Integer i1 = 1, i2 = 2;
swap(i1, i2);
System.out.println("i1: " + i1);
System.out.println("i2: " + i2 );
Integer a = 1;
if(a.intValue() == 2){
System.out.println("xx");
}
}
运行结果如下:
什么?1==2?
问题出在哪里呢?这里定义Integer a =1, 怎么就等于2了呢?
注意:不明白反射的请自行百度。
问题在这里:
field.setInt(i1, i2.intValue());
看下Field的源码:
如果还没发现问题的话,请把下面代码运行一下。
public static void destroyInteger(){
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
Integer i1 = 20;
Integer i2 = 30;
int tmp = i1.intValue();
field.set(i1, i2.intValue());
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
destroyInteger();
Integer a = 20;
System.out.println(a.intValue());
}
如下是我的解释:
1. Integer是int的包装对象
2. 为了提高性能,java的设计者在设计Integer和Long的时候,用到了享元模式,启动的时候,会缓存-128~127到常量池中,这些都是对象哦,不是简单的数字。
3. 在初始化局部变量Integer a=20的时候,发生的事情是,从常量池中取Integer 20这个对象的地址,然后把地址给a对象。
4. 那么为什么a的值是30呢?原因是Field的方法setInt传入的是Object,是一个对象,多以发生的事情是这样的。
Integer 20这个对象,被赋值为30。所以破坏了Integer。其他地方,用到Integer 20的时候,值都是30了。
所以,反射不要乱用哦,可能不小心,就写出bug来了。而且这种bug如果出现在大一点的系统里面很难排查。当然专业人员还是很快可以查出来的,哈哈。
总结一下这里的知识点:
- -128~127的整数,在启动的时候,就在jvm常量池中初始化了,程序中用到这些值的包装对象,都是直接从常量池中取。
- 反射的时候,Field的setXXX方法,传入的是对象,所以用的时候要注意,如果不是临时对象(尤其是jvm里面一直缓存的对象),不可以随便这样set。