如何理解Java只有值传递?

相信学习过C++和Java的同学肯定会在值传递和引用传递这两种传递方式上纠结一番,我之前也是傻傻分不清,今天就来说说它俩的区别。

首先,在理解值传递和引用传递之前,需要先清楚形参和实参的概念。

形参和实参

形参,指的是我们在定义一个函数时使用的参数,其目的是用于接收调用改函数时传入的参数,而在调用处传入的参数我们称之为实参
看代码更加直观:

public static void main(String[] args){
    int num = 1;
    this.method(num);
	System.err.println(num);
}
public static void method(int num){
    num = 2;
}

值传递和引用传递

先说一下它们的概念:

值传递:是指在调用函数的时候,将实参的原始数据复制一份给函数,在函数内部修改参数时,并不会影响到实际参数。也就是说,值传递只会改变形参,不会影响实参。

引用传递:是指在调用函数时,将实参的地址传递给函数,这样在函数内部对参数的修改将影响到实际参数。

这里需要注意的是,不要简单的通过传入参数的类型来判断是值传递还是引用传递,传递的参数是值不一定是值传递,参数的参数是引用类型也不一定是引用传递。判断是值传递还是引用传递,跟传入的参数类型是一点关系都没有。

下面举三个例子,涵盖上面所说的情况:

当传入的参数是基本数据类型

public static void main(String[] args){
    int num = 1;
    System.out.println("实参修改前:" + num);
    this.method(num);
	System.out.println("实参修改后:" + num);
}
public static void method(int num){
    System.out.println("形参修改前:" + num);
    num = 2;
    System.out.println("形参修改后:" + num);
}

打印结果:

实参修改前:1
形参修改前:1
形参修改后:2
实参修改后:1

可以发现,传递基本数据类型时,在函数中修改的仅仅是形参,对实参的值的没有影响。

扫描二维码关注公众号,回复: 14891067 查看本文章

需要明白一点,值传递不是简单的把实参传递给形参,而是实参拷贝了一个副本,然后把副本传递给了新参。下面用图来说明一下参数传递的过程:
在这里插入图片描述

图中num是实参,然后创建了一个副本temp,把它传递个形参value,修改value值对实参num没有任何影响。

传递类型是引用类型

public class User {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public class TestUser {
    public static void main(String[] args) {
        User user = new User(18, "zhangsan");
        System.out.println("修改对象前:"+user);
        changeUser(user);
        System.out.println("修改对象后:"+user);
    }

    private static void changeUser(User user) {
        user.setAge(20);
        user.setName("lisi");
    }
}

打印结果:

修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=20, name='lisi'}

可以发现,传过去的user对象,属性值被改变了。由于,user对象存放在堆里边,其引用存放在栈里边,其参数传递图如下:

在这里插入图片描述

user是对象的引用,为实参,然后创建一个副本temp,把它传递给形参user1。但是,他们实际操作的都是堆内存中的同一个User对象。因此,对象内容的修改也会体现到实参user上。

传递类型是String类型(Integer等基本类型的包装类等同)

public class TestStr {
    public static void main(String[] args) {
        String str = new String("zhangsan");
        System.out.println("字符串修改前:"+str);
        changeStr(str);
        System.out.println("字符串修改后:"+str);
    }

    private static void changeStr(String str) {
        str = "lisi";
    }
}

打印结果:

字符串修改前:zhangsan
字符串修改后:zhangsan

咦,看到这是不是感觉有点困惑。按照第二种情况,传递参数是引用类型时,不是可以修改对象内容吗,String也是引用类型,为什么在这又不变了呢?

再次强调一下,传递参数是引用类型,并不代表就是引用传递,其实它还是值传递。此时的 lisi 和上边的 zhangsan 根本不是同一个对象。画图理解下(这下面的堆栈图涉及到StringTable知识,下面的图以jdk1.8为基础绘制):
在这里插入图片描述

图中,str是对象 zhangsan 的引用,为实参,然后创建了一个副本temp,把它传递给了形参str1。此时,创建了一个新的对象 lisi ,形参str1指向这个对象,但是原来的实参str还是指向zhangsan。因此,形参内容的修改并不会影响到实参内容。所以,两次打印结果都是zhangsan。

第三种情况和第二种情况虽然传递的都是引用类型变量,但是处理方式却不一样。第三种情况是创建了一个新的对象,然后把形参指向新对象,而第二种情况并没有创建新对象,操作的还是同一个对象。如果把上边changeUser方法稍作改变,你就会理解:

private static void changeUser(User user) {
    //添加一行代码,创建新的User对象
    user = new User();
    user.setAge(20);
    user.setName("lisi");
}

运行以上代码,你就会惊奇的发现,最终打印修改前和修改后的内容是一模一样的。 这种情况,就等同于第三种情况。因为,这里的形参和实参引用所指向的对象是不同的对象。因此,修改形参对象内容并不会影响实参内容。

修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=18, name='zhangsan'}

总结

从以上三个例子中,我们就能理解了,为什么Java中只有值传递,并没有引用传递。值传递,不论传递的参数类型是值类型还是引用类型,都会在调用栈上创建一个形参的副本。不同的是,对于值类型来说,复制的就是整个原始值的复制。而对于引用类型来说,由于在调用栈中只存储对象的引用,因此复制的只是这个引用,而不是原始对象。

最后,再次强调一下,传递参数是引用类型,或者说是对象时,并不代表它就是引用传递。引用传递不是用来形容参数的类型的,不要被“引用”这个词本身迷惑了。

  1. 参数传递时,是拷贝实参的副本,然后传递给形参。(值传递)
  2. 在函数中,只有修改了实参所指向的对象内容,才会影响到实参。以上第三种情况修改的实际上只是形参所指向的对象,因此不会影响实参。

猜你喜欢

转载自blog.csdn.net/Your_Boy_Tt/article/details/128399599
今日推荐