0.上节我们将String类型的a和b在方法调用中改值,这节我们要在方法调用中将两个int 类型的a和b的值交换
众所周知,a和b是基本数据类型,会在一顿操作猛如虎之后被弹栈,不会对main中的值造成任何改变,但是今天还是来挑战下自我,但是考虑到基本数据类型值存储在栈中,引用数据类型值存储在堆中,所以计划利用int的包装类Integer来实现
1.如果是2个int类型的数值,是无法进行值交换的,因为基本数据类型就是一个数值,无法向引用数据类型一样传入其他方法进行"偷梁换柱"
public static void main(String[] args) throws Exception {
int a = 1;
int b = 2;
swap0(a, b);//无用
System.out.println(a);//1
System.out.println(b);//1
}
2.可以用反射的方式,修改Integer的value字段,看样子是成功了
问题1:Integer是final类,咋会说改就改??
-- 我们知道 "final修饰的类不能被继承, final修饰的方法不能被重写,final修饰的字段不能被修改",引用类型记录的是地址值,Integer a 和 b 指向的地址值从头到尾确实没变,但是那个地址下的值在经过反射后被换掉了,所以这里没问题
问题2:swap1(int a,int b)相当于只将基本数据类型(相当于一个数值)传入了一个方法进行操作,应该是对main中的a和b的值毫无影响的,但是现在结果却出乎意料,究竟是人性的扭曲还是道德的沦丧?
-- 这里涉及到Java中Integer$IntegerCache对Integer值的缓存和优化问题,反射过程实质上会修改缓存中相应值对应的地址处的值
其中的原委,且听我娓娓道来...
1)main中地址值 a = {Integer@527},a = {Integer@528},
2)Scala中,内部类属于实例的,而Java中,内部类是属于类的,所以此时,我从a中取得的Integer$IntegerCache.cache[]就是这里所有Integer实例所共享的Integer内存缓存,缓存范围为-128~127
Debug查看Integer$IntegerCache的cache[]中对应的Integer@527= 1 , Integer@528= 2
3)经过field.set(a, newA); field.set(b, newB);时, a,b被自动装箱为Integer,Integer$IntegerCache中数值1对应的地址值被反射设值为100,数值2对应的地址值被反射设值为200
4)返回到main中,a和b对应的地址值未变,但是地址值对应的数值却变化了
3.总结上面的经验,我们利用反射,将a和b进行互换赋值就OK啦?? 但是发现结果是a=2,b=2
分析:
1)首先在 field.set(a, newA); field.set(b, newB);时,a、b、newA、newB、被自动装箱,
2) 执行 field.set(a, newA)前a=1,、newA=2 ,执行时获取1在IntegerCache.cache[]地址值,将该位置的值替换为2
3)field.set(b, newB)时,debug中newB=1,这是IDE显示的一个bug,实际上newB引用的是a的地址值,a处地址值此时对应的值是2,所以这步执行完后,b的值依然是2.换句话说,此时a和b指向的是同一个地址值的数值
那要怎么解决同一个地址值指向的这个问题??
4.既然在缓存范围之内(-128~127)存在这个问题,那如果a和b都不在这个范围之内,是不是就不存在指向同一地址值的问题了?
分析:发现a和b的数值根本没有交换, 走debug瞧一瞧
发现a和b的地址值分别是527和528,不在cache[]数组地址值256~798范围中
进入field.set(b, newB)发现2个全新的地址值
原因是在同一类加载器中,value数值处于(-128~127)范围的Integer在包装时,会直接将地址值指向IntegerCache中的缓存对象,超出范围的会每次都开辟一片新空间new一个Integer实例出来,所以main()中对1000、2000的包装地址和swap3()中对1000、2000的包装地址指向不同,因此在swap3()中进行a和b的反射改值操作对main中的a、b数值毫无影响!
那要怎样解决(-128~127)范围外无法调换数值的问题??
--将swap3(int a, int b)改为swap3(Integer a, Integer b),让main中a和b的地址值传递下来
field.set(a, newA);
field.set(b, newB);
可见,
newA和newB都没有直接指向缓存中的地址,而是new Integer 新开辟空间,因此不存在指向同一地址值的问题;
swap3中a和b类型由int换成Integer类型后也与main()中的a和b指向了统一地址值,因此在(-128~127)范围外可以顺利反射改变main()中的a、b值!
5.那么在(-128~127)范围内的地址值指向同处的问题该如何解决??
把newA和newB新开辟空间,在field.set(a, newA); field.set(b, newB);时就不会产生b指向a存储地址的问题
发现结果竟然还是 a=2,b=2
看了 valueOf源码才知道,这个方法有缓存先去缓存中拿值了
所以我们不能用这个方法,我们自己new Integer(x)
ok了,完美解决
重点圈起来
WoW~~~ 夜晚是猿猿们的天堂,但也要注意身体,猿兄们 Good Night !