Java参数传递(值传递还是引用传递)

基本类型和引用类型的区别

int num = 10;
String str = "hello";

1607659804435

也就是说,对于基本类型的数据变量,他的值是直接存储在变量中,而str是一个引用类型的变量,变量中保存的是我们实际的对象在堆内存中的地址,而我们真实的对象,其实是存储在堆空间中的。

下面我们再来看看==的作用

num=20
str="java"

经过这样的操作,内存中的数据又经历了如何的变化?实际是下面这个样子的。

1607660117448

对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
如上图所示,"hello" 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)

值传递和引用传递的区别

在介绍Java到底是值传递还是引用传递之前,我们先来看看到底什么是值传递,什么是引用传递。

值传递(pass by value)指的是在把实参传递给形参的时候,将实际的参数复制一份传递给函数的形参,也就是说,在函数内部对形参的修改,不会改变原来实参的值。

引用传递(pass by reference)指的是在实际的函数调用时,将实际参数的地址传递给函数的形式参数,也就是说其实形参和实参保存的是堆中同一个对象的地址。如果在函数中对堆中的对象属性进行修改,那么不用说,实参对应的此对象的属性也会改变。

基本类型作为参数传递

接下来我们步入正题,看看java到底是值传递还是引用传递,看看下面这段代码。

public class ValueAndRef {
    public static void main(String[] args) {
//        对于基本类型的传递
        int num=10;
        System.out.println("调用change()方法前num的值是:"+num);//10
        change(num);
        System.out.println("调用change()方法后num的值是:"+num);//10

    }
    public static void change(int number){
        number=20;
    }
}
// 输出结果
调用change()方法前num的值是:10
调用change()方法后num的值是:10

首先我在main函数中定义了变量num=10,然后打印调用change()方法,并且把Num作为形式参数传递给此方法对其值进行修改,我们可以看到在调用change()方法修改之后,主方法中的num变量的值仍然没有改变,那么对应在内存中究竟是怎么一回事呢?

1607663368127

可以看到在内存中其实是这样的一种情况,我们知道,每一个方法都对应一个栈帧,其中我们在main()方法中声明的变量存储在main()方法的局部变量表中,当我们把num作为实际参数传递给change()方法时候,那么此是就会在change()方法的局部变量表中多出来一个变量number,所以,我们可以从底层看到,其实main()函数中的实参传递给change()方法中后,两个变量实际上是相互独立的,相互不会影响,所以,在上面尽管调用了change()去修改num的值,但是实际上修改的是change()自己局部变量表中number的值,并不会影响main方法中的num值,所以问题就很清晰了,对于基本数据类型的变量,在java中实际上是值传递的过程,不会修改原来的值。

对象作为参数传递

相比争论最多的就是对象作为参数传递的时候,请看下面的代码。

public class ValueAndRef {
    public static void main(String[] args) {
        Persion persion=new Persion("小明",10);
        System.out.println("调用change()方法之前:"+persion.toString());
//        调用方法对person对象进行修改
        change(persion);
        System.out.println("调用change(0方法之后:"+persion.toString());

    }
    public static void change(Persion persion){
        persion.setName("小菊");
        persion.setAge(20);
    }
}
//输出结果
调用change()方法之前:Persion{name='小明', age=10}
调用change(0方法之后:Persion{name='小菊', age=20}


class Persion{
    private String name;
    private int age;




    public Persion(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Persion{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

看来是不是有些出乎意料,竟然把我们person对象的属性值修改了,这是为什么呢?下面我们看看对象的内存布局就明白了。

1607664334338其实上面的那一段代码,在内存中的逻辑是上图中所示那样的,在上面基本数据类型和引用类型的区别中,我们已经说明,引用数据类型实际上存储的是对象在堆内存中的地址,在main()函数中,把person引用存储的内容传递给了change()函数的形参,实际上传递的是堆空间中对象的地址,也就是这两个引用,在内存中其实指向的是堆空间中的同一个对象。就像上面画的所示,所以,你在change中对堆空间中这个对象进行修改,那么自然person引用指向的对象内容也会发生改变。

  • 现在我们再来看看另一段代码:
public class ValueAndRef {
    public static void main(String[] args) {
        Persion persion=new Persion("小明",10);
        System.out.println("调用change()方法之前:"+persion.toString());
//        调用方法对person对象进行修改
        change(persion);
        System.out.println("调用change(0方法之后:"+persion.toString());

    }
    public static void change(Persion persion1){
        persion1=new Persion("小菊",20);
    }
}
//输出结果
调用change()方法之前:Persion{name='小明', age=10}
调用change(0方法之后:Persion{name='小明', age=10}

看到没有,现在调用change()方法后,person引用指向的对象的内容竟然没有发生改变,我们再来看看此时的内存布局:

  • 在我们进入到change()方法后,没有执行persion1=new Persion("小菊",20);这一句代码之前,内存布局其实是这样的,此时两个引用仍然指向堆内存的同一块区域。

1607664970806

  • 当我们执行persion1=new Persion("小菊",20);后,我们的内存布局是这样的:

1607665266079

也就是说此时change()方法中的person1指向了堆内存中的另一块内存区域,现在main()方法和change()方法中的两个引用分别指向堆内存中的不同区域,相互独立互不影响,所以,在change()方法中对对象内容修改,不会影响main()函数中的对象。

好了,现在三种情况都讲完了,对于基本数据类型很好理解,但是对于引用数据类型,我们就要想明白,引用变量里面实际存储的是对象在堆空间中的真是地址,在发生方法调用需要传递对象的时候,其实是把对象的真是地址复制了一份作为实参传递给了形参,

  • 现在,我们再来总结一下值传递和引用传递的区别:
值传递引用传递
区别会创建副本不会创建副本
影响函数中无法改变原始的对象值函数中会改变原始的对象值

经过上面的分析,可以看到值传递和引用传递实际上的区别并不是传递的内容,而是实际参数到底有没有复制一份给形参,从这个角度将,java肯定就是值传递,对于基本数据类型,是把值复制了一份给形式参数,而对于引用类型来说,其实就是把对象存储的地址复制了一份给形参,所以在判断我们的实参内容到底有没有收到影响,就要看你传递的是什么,如果你传递的是地址,那么就需要看形式参数看的指向到底有没有发生变化,所以总的来说,java是值传递,对于引用类型,传递的是对象的地址,这一点没错。

关于String

String类是个特殊的类,对它的一些操作符是重载的,如:

String str = “Hello”; 等价于String str = new String(“Hello”);

String str = “Hello”;

str = str + “ world!”;等价于str = new String((new StringBuffer(str)).append(“ world!”));

因此,你只要按上面的方法去分析,就会发现String对象和基本类型一样,一般情况下作为参数传递,在方法内改变了值,而原对象是不会被改变的。

参考资料

[1] https://www.zhihu.com/question/31203609
[2] https://www.cnblogs.com/hpyg/p/8005599.html

扫码关注作者公众号,获取更多关于大数据,java,数据结构与算法,jvm,数据库等资料。
在这里插入图片描述

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值