java引用:值传递和引用传递(全)

前言

记得刚学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

三、结语

道阻且长,行则将至,行而不辍,未来可期,加油。

原创不易,如果你觉得文章不错,对你的进步有那么一点帮助,那么就给个小心心,如果觉得文章非常对你的胃口,那么欢迎你关注我,或者关注个人的微信公众号 程序猿每日分享,这里有资源,有内推,有和你志同道合的朋友,咱们一起打怪升级。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿每日分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值