点击上方“java患者” 可以订阅哦!
关于Integer的讲解
1
main方法中有a,b两个变量,且为Integer类型。通过调用本类的swap方法将二个值互换,并在main方法中输出结果。
——AA
实例代码如下方:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("before swap: a="+a+",b="+b);
swap(a,b);//这是一个Integer交换值的方法
System.out.println("after swap: a="+a+",b="+b);
}
private static void swap(Integer num1,Integer num2){
Integer tmp = num1;
num1 = num2;
num2 = tmp;
}
1
调用swap方法是传递是的a,b的引用给num1和num2,方法调用完毕时,a和b指向的地址并未发生改变。
那么我们该如何传递?
1. 我们先分析Integer a = 1;到底做了些什么?我们看一下他的字节码:
第一条可以看出,JVM采用iconst指令将常量1压入栈的栈顶,然后调用Integer.valueOf(1),进行装箱操作。
2. 我们来看Integer内部如何赋值的。
下面我们来看下Integer里面的源代码:
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
也就是说,实际上a=1;内部的最终操作说对Integer里面的value进行赋值。那我们在swap方法去改变这个value的值可解决我们的问题,显然,我们可以利用到反射来完成。
下面是进一步代码:
private static void swap(Integer num1,Integer num2){
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int tmp = num1;
field.set(num1,num2);
field.set(num2,tmp);
} catch (Exception e) {
e.printStackTrace();
}
}
运行后的结果为:
before swap: a=1,b=2
after swap: a=2,b=2
3. 为啥是这样这,a确实改过来了,b为啥改不了?
这牵扯到装箱和拆箱,我们再来看下Integer里的valueof方法:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
当i的值在[IntegerCache.low, IntegerCache.high]区间内,从cache数组缓存中取。low值是-128,hight值是127。也就是说,如果值是[-128,127]间是从缓存中取。而cache数组元是不同整数-128到127从小到大排列。如cache[129]=1;如果不在这范围内,就进行new Integer(i)。
我们重点来看这三行代码
int tmp = num1;
field.set(num1,num2);
field.set(num2,tmp);
在field.set(num1,num2)过程中,由于set的二个参数都为Object。第一步先取num2值进行装箱,num2是2,在[-128,127]间,从缓存中取,下标是2+128=130,即cache[130]为2。第二步是赋值,相当于cache[129]=2;
在field.set(num2,tmp);过程中,tmp是值为1,自动装箱从缓存中取,索引为129。cache[129]已是为2了,其赋值相当是cache[130]=2,
所以最终输出结果是a为2,b为2。
最后,我们有一个结论,当我们的值在[-128,127]间从cache缓存中取。所以有一下两种情况:
Integer a = 1;
Integer b = 1;
System.out.println(a==b);//true
Integer a = 128;
Integer b = 128;
System.out.println(a==b);//false,不在[-128,127]内,比较地址。
4. 最终解决方案
方案一:
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int tmp = num1;
field.set(num1,num2);
field.set(num2,new Integer(tmp));
} catch (Exception e) {
e.printStackTrace();
}
此时是以一个新的对象,不会从缓存中取。
方案二:
try {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int tmp = num1;
field.set(num1,num2);
field.setInt(num2,tmp);
} catch (Exception e) {
e.printStackTrace();
}
不产生装箱操作,直接set一个int类型。就不会从缓存中取。
历史文章
限时下载|500G编程资料:springboot、vue、mybatis源码分析、vue、项目实战、数据结构与算法、C语言等
点击蓝字关注我们