深刻理解Java方法中的参数传递问题

1.引子

对于初学者而言,方法的参数传递是个不好理解的东西。对于不少行业老司机,也不一定能完全说得清楚。有的人说,基本数据类型参数做值传递,引用数据类型做地址传递;还有的人说,它们都是做地址传递…。这些说法可以认为是正确的,但也不完全正确。很多说法都没有接触到实质,从计算机原理来说,不论何种数据类型,它们的参数传递方式是一致的,都是传递“指针的副本”。

2.基本数据类型参数传递

对于基本数据类型而言,它本身只有“值”,没有方法、属性。当其做局部变量时,它直接存在栈中。系统自动分配一个栈空间,去储存它的“值”,其实这个“值”可以看做一个“地址”,对于基本数据类型而言,将这个“地址”当做值来用了而已(在JVM内部实现原理上有明显的体现,如JVM的基本模型0op-Klass,这里的Oop不是指面向对象编程,而是指Orindal Object Pointer普通对象指针,为什么这里的指针要加“普通”二字,就是因为引用数据类型的句柄是“一般情况下的指针”,而基本数据类型是将“指针”或者说“地址”来当作“值”使用了,这可以说是一种“特殊的指针”)。可以这么认为此时“值”=“储存的地址值”(注意这里的“储存的地址值”不是这个基本数据类型变量本身的地址)。在方法调用时,方法得到的是参数值的副本,方法不能改变传递给它的参数变量的内容

  • 基本数据类型在栈(Stack)内存中的分布
    在这里插入图片描述
  • 参数传递示意图
    在这里插入图片描述
//定义基本数据类型的变量的自增运算方法
    public static int inc(int num)
    {
        num = num + 1;
        return num;
    }
 public static void main(String[] args)
    {
        
        int a = 4;
        System.out.println("变量a的原始值是:" + a);		//4
        System.out.println("变量a作为参数进行方法调用后,方法的返回值:" + inc(a));	//5
        System.out.println("变量a作为参数进行运算后,a本身的值是:" + a); //只改变其副本的值,a仍为4
    }
  • 输出结果

在这里插入图片描述

3.引用数据类型参数传递

对于引用数据类型而言,我们一般要通过它的引用去访问、更改其属性或调用方法(匿名对象除外,即类似于new T().name或new T().setName()的形式)。一般情况下用"T t = new T( )"的形式声明并创建对象,“new T()”创建的对象被系统分配在堆(Heap)内存空间中;而此对象的引用t(或称为“句柄”)被分配在栈(stack)内存空间中,它的值是一个指向刚创建的对象的在堆内存中的首地址。

  • 引用数据类型的内存分布
    在这里插入图片描述

(1)引用数据类型参数可影响对象状态

当引用数据类型变量作为方法的参数时,方法得到的是这个对象引用的副本,因为对象的引用与这个引用的副本同时引用同一个对象,所以方法体内的一些操作会直接影响这个对象的内容或状态

  • 示意图1
    在这里插入图片描述
//定义一个给学生名字添加统一前缀的方法
 public static void formatName(Student stu)
    {
        if (!stu.getName().startsWith("stu_"))
        {
            stu.setName("stu_" + stu.getName());
        }
    }
 public static void main(String[] args)
    {
  
        Student s1=new Student("王晓莉","女");
        Student s2=new Student("李天恒","男");
        System.out.println("s1同学原来的名字:"+s1.getName());
        formatName(s1);     //此方法得到的是句柄s1的副本
        /*
         * 对象的引用及这个引用的副本同时引用同一个对象,
         * 所以方法内的一些操作会直接影响这个对象的内容或状态
         * s1的名字将被改为stu_王晓莉
         */
        System.out.println("方法调用后s1同学的名字:"+s1.getName());  
    }
  • 输出结果
    在这里插入图片描述

(2)引用类型参数不能改变引用关系

也是因为方法得到的参数是对象引用的副本,而不是对象引用本身,所以在调用方法后这个对象引用不会被更改为去引用另一个对象

  • 示意图2
    在这里插入图片描述
//定义一个试图更改句柄指向的方法  
   public static void swap(Student stu1, Student stu2)
    {
        Student tmp = stu1;
        stu1 = stu2;
        stu2 = tmp;
        System.out.println("在方法体内对象引用互换后,s1对应的对象引用副本stu1的对应学生名字:"+stu1.getName());
        System.out.println("在方法体内对象引用互换后,s2对应的对象引用副本stu2的对应学生名字:"+stu2.getName());
    }
  public static void main(String[] args)
    {
   
        Student s1=new Student("王晓莉","女");
        Student s2=new Student("李天恒","男");
        swap(s1,s2);//方法体代码执行时,只用到了s1 s2这两个对象引用的副本stu1 stu2
        /*
         *方法调用后,对象引用的副本被交换了,而对象引用本身没有被交换
         */
        System.out.println("在调用方法 swap(s1,s2)后,s1对象引用本身的对应学生名字:"+s1.getName());
        System.out.println("在调用方法 swap(s1,s2)后,s2对象引用本身的对应学生名字:"+s2.getName());
        
    }
  • 结果输出
    在这里插入图片描述

4.总结

从以上试验结果来看,方法的参数传递方式都是传递“指针的副本”,或者说是“值传递”。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值