前言
记得刚学java到工作一两年时对java的值传递,引用传递的理解一直是一种朦胧的状态,一直感觉前面好像蒙了一层雾,有种雾里看花的感觉,所以有了这篇文章,想让大家能更加深刻的理解其中的原理。
一、问题切入
全部都是文字,没有程序,不能够更好的切入问题,我们看下面一段程序(1):
public class Test {
private void print(int a){
System.out.println("-----1----" + a);
change(a);
System.out.println("-----2----" + a);
}
private void change(int a){
a = 2;
}
public static void main(String[] args) {
int a = 0;
Test aTest = new Test();
aTest.print(a);
}
}
问1:-----1,2----后边的输出的a值分别是多少?
看完程序(1),我们再看程序(2):
class A{
public B b;
public A(B b){
this.b = b;
}
public B getB(){
return b;
}
}
class B{
}
public class Test {
public void changeObj(A a){
a = new A(new B());
}
public void changeAttr(A a){
a.b = new B();
}
public static void main(String[] args) {
B b = new B();
A a = new A(b);
Test test = new Test();
System.out.println("---1---" + System.identityHashCode(a));
test.changeObj(a);
System.out.println("---2---" + System.identityHashCode(a));
System.out.println("---3---" + System.identityHashCode(a.getB()));
test.changeAttr(a);
System.out.println("---4---" + System.identityHashCode(a.getB()));
}
}
问2:-----1,2,3,4----后边的输出的a值分别是多少?
二、问题分析
分析前我们首先把值传递和引用传递的定义抛出来(摘自百度百科):
- 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
- 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
然后我们再看程序的运行结果:
程序(1)的输出为:
-----1----0
-----2----0
程序(2)的输出为:
---1---1128032093
---2---1128032093
---3---1066516207
---4---443308702
其次抛出我们自己的两条结论:
- 严格意义上说Java中只存在值传递;
- Java中的引用传递是半个引用传递。
2.1 值传递
我们先看程序(1)的输出,我们发现经过方法的赋值后,原来的值并没有被改变。根据前边我们抛出的定义我们能很清楚的看到,这是明显的值传递。
对于java中的方法来说,每个方法的执行,都代表着一个栈帧在虚拟机栈中入栈到出栈的过程。我们程序中的a值在传入change方法时,并不是把a的对象直接传入了,而是把a的副本传入了栈中,放在局部变量表进行操作,因此在change方法中对a的修改,并不会影响到外边的参数a。
我们简单的用文字表述感觉想是在搬概念,我们今天尝试一种超硬核方式来说明这个问题,相信看过后会让大家理解的更深刻,更明白,我们通过javac和javap -c得到该类的汇编指令如下代码所示:
Compiled from "Test.java"
public class testa.Test {
public testa.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: new #12 // class testa/Test
5: dup
6: invokespecial #13 // Method "<init>":()V
9: astore_2
10: aload_2
11: iload_1
12: invokespecial #14 // Method print:(I)V
15: return
}
查看以上的字节码指令,我们主要看main方法中的这几条:
0: 将a的值推送到栈顶,此时是0
1:将栈顶int型数值(0)存入到第2个本地变量
9:将栈顶引用类型的输入放入第3个本地变量表
10:将第3个的引用类型的本地变量推送至栈顶
11:将第2个本地变量推送至栈顶
通过上边的字节码指令,我们能清楚的看到,方法中的修改根本不会影响a原来的值,在本地变量表中存放的地址都不一样。
2.2 引用传递?
“引用传递”这儿我们采用和值传递一样的思路,由于加上输出之后,整个字节码会比较多,不利于分析,我们把输出注释掉,生成的字节码如下所示:
Compiled from "Test.java"
public class org.xingzejiangzhi.shop.order.Test {
public org.xingzejiangzhi.shop.order.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void changeObj(org.xingzejiangzhi.shop.order.A);
Code:
0: new #2 // class org/xingzejiangzhi/shop/order/A
3: dup
4: new #3 // class org/xingzejiangzhi/shop/order/B
7: dup
8: invokespecial #4 // Method org/xingzejiangzhi/shop/order/B."<init>":()V
11: invokespecial #5 // Method org/xingzejiangzhi/shop/order/A."<init>":(Lorg/xingzejiangzhi/shop/order/B;)V
14: astore_1
15: return
public void changeAttr(org.xingzejiangzhi.shop.order.A);
Code:
0: aload_1
1: new #3 // class org/xingzejiangzhi/shop/order/B
4: dup
5: invokespecial #4 // Method org/xingzejiangzhi/shop/order/B."<init>":()V
8: putfield #6 // Field org/xingzejiangzhi/shop/order/A.b:Lorg/xingzejiangzhi/shop/order/B;
11: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class org/xingzejiangzhi/shop/order/B
3: dup
4: invokespecial #4 // Method org/xingzejiangzhi/shop/order/B."<init>":()V
7: astore_1
8: new #2 // class org/xingzejiangzhi/shop/order/A
11: dup
12: aload_1
13: invokespecial #5 // Method org/xingzejiangzhi/shop/order/A."<init>":(Lorg/xingzejiangzhi/shop/order/B;)V
16: astore_2
17: new #7 // class org/xingzejiangzhi/shop/order/Test
20: dup
21: invokespecial #8 // Method "<init>":()V
24: astore_3
25: aload_3
26: aload_2
27: invokevirtual #9 // Method changeObj:(Lorg/xingzejiangzhi/shop/order/A;)V
30: aload_3
31: aload_2
32: invokevirtual #10 // Method changeAttr:(Lorg/xingzejiangzhi/shop/order/A;)V
35: return
}
我们可以看到changeAttr方法的8 putfield将为指定的类的实例域赋值,所以会生效:
8: putfield
我们也可以看到changeObj方法,最后是存储到了自己的方法的所占有栈的本地变量表的第二个位置,并没有修改原来的值:
0: new #2 // class org/xingzejiangzhi/shop/order/A
3: dup
4: new #3 // class org/xingzejiangzhi/shop/order/B
7: dup
8: invokespecial #4 // Method org/xingzejiangzhi/shop/order/B."<init>":()V
11: invokespecial #5 // Method org/xingzejiangzhi/shop/order/A."<init>":(Lorg/xingzejiangzhi/shop/order/B;)V
14: astore_1
15: return
三、结语
道阻且长,行则将至,行而不辍,未来可期,加油。
原创不易,如果你觉得文章不错,对你的进步有那么一点帮助,那么就给个小心心,如果觉得文章非常对你的胃口,那么欢迎你关注我,或者关注个人的微信公众号 程序猿每日分享,这里有资源,有内推,有和你志同道合的朋友,咱们一起打怪升级。