前言
关于传值机制,之前自己再Java基础中了解过,但是昨天晚上却有了一些新的认知,所以记录一下,以便后期复习,今天我们以一道面试题作为开头来引导
一.面试题
在main中定义了两个Integer a和b ,通过swap方法,交换两个值,请写出swap方法
二.常规思路(仅仅是思路)
我们需要通过一个中间变量(temp)保存其中一个值,然后再进行交换,我们来看一下
1 | public class Test_01 { |
结果:
1 | before swap a:1,b:2 |
这个我们大家都知道,在 Java 中基本类型之间在参数中是指传递,也就是说,这里我们的 num1 和 num2 只是 main 函数中 a 和 b 的值,而并非 a 和 b 本身,我们在函数中改变的仅仅是 num1 和 num2 这个两个临时变量的值,对于 main 函数中 a 和 b 的值并没有什么影响
三.引用传递呢?
在Java中引用传递其实也是值传递,只是这个引用它本身保存的是此引用指向的内存地址,所以会将这个内存地址的值传过去,而我们在函数中操作此临时变量就相当于操作原来的引用,因为此时你是通过地址来操作啊,就是c语言中的指针
那我们来看看将 int 变为Integer呢?
1 | public class Test_02 { |
这里会有一个自动装箱,后面解释。
打印结果
1 | before swap a:1,b:2 |
打印结果并没有改变,那这是为什么呢????
我们再来看一下String类型(这个之前尧哥面试还问过我,我都说错了)
1 | public class Test_03 { |
打印结果
1 | before swap a:1,b:2 |
还是之前的值,String和Integer不都是引用类型吗,为什么没有改变呢???
内存图解
也就是说,如果我们在函数中将传进来的引用类型给它赋一个新的值,那么其改变并不会影响外部引用的值,就像上面那样,那什么时候会在函数内部的改变会影响外面的值呢?我们在Java中不经常传自己定义的JavaBean对象进去吗???我们来看看
函数内的改变影响函数外的值的例子
自定义一个User对象
1 | class User{ |
测试案例
1 | public class Y_01_测试值传递 { |
我们来分析一下:
总结出什么呢?
1. 就是说当我们在函数内部直接给传进来的引用赋一个新的值的时候,这种影响并不会影响外面的引用的值,它仅仅是将函数中复制的那个临时变量给改了,并未改变函数外部的值比如下面这种
1 | public void testparametertransfer02(User u){ |
2. 而当我们在函数内部改变引用对象内部的属性的值的时候,这种影响会扩散到函数外部,因为在函数内部复制的那个引用指向的跟函数外部指向的是一个地址,你改变其内部属性的值就是改变了外部指向的值,如下:
1 | public void testParameterTransfer01(User u){ |
我们改变了传进来的User对象内部的值,那么外部的值也就被改变了
所以说在函数内部通过赋值的操作就不要想了,行不通的弟弟。
包装类分析
1 | public class Test_03 { |
现在我们分析出来,在函数内部直接交换是行不通的。
我们都知道八大基本数据类型都对应其一个包装类,int–>Integer,byte–>Byte等。当我们用一个包装类来接收一个基本类型的时候发生了什么呢?比如
1 | Integer a = 1; |
这里会发生自动装箱,先不考虑这个问题
这里这个Integer对象将这个1到底存到了哪里呢??
Integer源码
其实呢,Integer这个对象会将这个基本数据存放到其内部的一个 int value中
那么其他的包装类也一样。
String不是一个包装类,但是String是一个对象,在String对象这个内部有一个char[] 数组,String就是将值保存到了这个char[] 数组中的,而且这个char[] 数组也是final修饰的
既然我们都说了,我们给传进来的引用直接赋值是不行的,我们只能想法去改变其内部属性,对吧。也就是说我们要去改变这些包装类和String类内部真正保存数据的那个东西,就像这样:
1 | public void testParameterTransfer01(User u){ |
四.反射技术
看代码
1 | /** |
以上是我们使用反射技术来实现的交换,有没有什么问题
问题大了去了打印结果:
1 | before swap a:1,b:2 |
分析一波
在我们执行下面这句代码的时候会发生一个自动装箱操作
1 | Integer a = 1;//自动装箱 = Integer.valueof(1) |
而这个自动装箱操作内部呢,有一个Integer的缓冲数组,叫做IntegerCache
,IntegerCacheIntegerCache内部有一个已经初始化好的Integer[],而这个Integer[]就是我们的真正的缓存数组
这个IntegerCache的low的范围是-128而high是+127,也就说,如果我们这个穿进来的这个数如果大于-128且小于+127,那么就将已经初始化好的这个缓存数组的值给它,也就是说现在这个a和这个b指向的是这个缓冲数组中的的值。
现在我们比如执行Integer a = 1;那么这个就会返回 IntegerCache.cache[1+(-(-128))] = IntegerCache.cache[129]
而这个IntegerCache.cache[129]的值正好是1,就是人家帮咱们初始化好的,提高效率的。那么依次类推IntegerCache.cache[130]就等于2,对吧,这里可以看懂吧
那么我执行这段代码时
1 |
|
Field field = Integer.class.getDeclaredField(“value”);获取到Integer的value属性
field.set(num1,num2);将num1对象中的value值设置为num2中的value值,就是将IntegerCache.cache[129]的值设置为IntegerCache.cache[130](因为num2是IntegerCache.cache[130]),所以如下
1 | IntegerCache.cache[129] = num2 = 2 ===> IntegerCache[129]=2 |
在当我们执行下面代码时,对发生什么呢???
1 | field.set(num2,temp); |
将num2中的value值设置为temp,也即将IntegerCache.cache[130]的值设置为temp,此时temp是一个基本类型,而我们的field.set()方法需要传进去两个Object类型,此时temp是int类型,所以这里又会有一个自动装箱操作,即
1 | Integer temp = Integer.valueof(temp); |
即这样,temp中保存的是num1的value值1
Integer temp = Integer.valueof(1);
此时它又去Integer的缓冲数组中去比较了对吧,于是返回了
1 | Integer temp = Integer.valueof(1) = IntegerCache.cache[129] |
而此时 IntegerCache.cache[129] 已经等于2了,我们之前分析的,所以这里
1 | temp = Integer.valueof(1) = IntegerCache.cache[129] = 2; |
所以只改变了一半,那怎么操作呢???
很简单
1 | field.set(num2,new Integer(temp)); |
这样就ok了
最终代码
1 | package yp.Java_Interview; |