Java的值传递和引用传递详细解答

了解实参和形参

  • 实际参数

    在调用有参数的方法时,主调用方法和被调用方法之间有数据传递关系。在主方法中调用一个方法时,方法名后面括号中的参数称为实际参数

  • 形式参数

    是在定义方法名和方法体的时候使用的参数,目的是用来接收调用该方法时传入的参数

  • 举个栗子

    package com.xyz.example;
    
    /**
     * @author 歪歪
     * @version 1.0
     * @date 2020/12/17 11:30
     */
    public class RTest {
    
        public void rg(String name){//rg方法参数 name 就是形式参数
            System.out.println(name);
        }
    
        public static void main(String[] args) {
            RTest rTest = new RTest();
            //这里调用RTrst类中的rg方法,其中传入方法中的 "歪歪" 就是实际参数
            rTest.rg("歪歪");
        }
    
    }
    
  • 总结概括

    形式参数是方法中用于接收实参内容的参数,实际参数是调用有参数的方法时候真正传递的内容。

了解基本数据类型内存存储方式

基本数据类型和引用数据类型在内存中的存储方式

20201217160524

  • 基本数据类型:存放在栈内存中,用完就消失。
  • 引用数据类型:在栈内存中存放引用堆内存的地址,在堆内存中存储类、对象、数组等。当没用引用指向堆内存中的类、对象、数组时,由 GC回收机制不定期自动清理。
  • 关于java堆内存和栈内存详细讲解,推荐查看这篇文章:基础知识篇——堆内存和栈内存

字符串常量池

  • 为字符串开辟一个字符串常量池,类似于缓存区。
  • 创建字符串常量时,首先坚持字符串常量池是否存在该字符串。
  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中。
  • 关于字符串常量池详细讲解,推荐查看这篇文章:String:字符串常量池

值传递和引用传递

在上述中,我们了解到,当我们在java中调用一个有参方法的时候,会把实际参数传递个形式参数。但是在计算机程序语言中,这个传递过程中传递的两种情况,即是值传递和引用传递。

下面我们来看程序语言中是如何定义值传递和引用传递(加粗为重点):

  • 值传递

    是指在调用方法时将实际参数复制一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数本身。

    举个栗子:

    /**
     * @author 歪歪
     * @version 1.0
     * @date 2020/12/17 15:22
     */
    public class RTest2 {
    
        public void add(int age){// 形式参数 age
            age = age - 10;
            System.out.println("add方法的age:" + age);
        }
    
        public static void main(String[] args) {
            RTest2 rTest2 = new RTest2();
            int age = 50;//实际参数 age
            rTest2.add(age);//传递实际参数 age
            System.out.println("main方法的age:" + age);
        }
    
    }
    

    输出结果为:

    add方法的age:40
    main方法的age:50
    

    从上面案例中发现,在add方法中我们修改了age的值,但是实际参数的值并没有被改变,是因为传递add方法的时候,会将实际参数的值复制一份传递到方法体中,不管这个方法体内对这个值做任何逻辑操作都不会影响到实际参数的值本身。

    图例说明:

    20201217163442

  • 引用传递

    是指在调用方法时将实际参数的地址直接传递给方法体中,那么在方法体中对参数所进行的修改,将会印象到实际参数。

    举个栗子:

    package com.xyz.example;
    
    /**
     * @author 歪歪
     * @version 1.0
     * @date 2020/12/17 15:46
     */
    public class RTest3 {
    
        public void add(User user){// 形式参数 user
            user.setName("张三");
            user.setAge(18);
            System.out.println("方法add的:" + user);
        }
    
        public static void main(String[] args) {
            RTest3 rTest3 = new RTest3();
            User waiwai = new User();
            waiwai.setName("歪歪");
            waiwai.setAge(18);
            rTest3.add(waiwai);// 实际参数 user
            System.out.println("main方法的:" + waiwai);
        }
    
    }
    
    class User{
        
        /**
         * 姓名
         */
        private String name;
        /**
         * 年龄
         */
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    输出结果为:

    方法add的:User{name='张三', age=18}
    main方法的:User{name='张三', age=18}
    

    通过输出结果发现,实际参数的值改变了,因为引用传递是将实际参数在栈中存储的地址直接传递给了方法体中的形式参数,所以在方法体中做操作,实际上还是地址相对应的堆内存存储的值进行改变。

    图例说明:

    20201217164938

  • 思考:

    案例一:为什么String类型的引用传递,它的实际参数不会被改变。

    代码如下:

    package com.xyz.example;
    
    /**
     * @author 歪歪
     * @version 1.0
     * @date 2020/12/17 17:02
     */
    public class RTest4 {
    
        public void add(String name){
            name = "张三";
            System.out.println("add方法的:" + name);
        }
    
        public static void main(String[] args) {
            RTest4 rTest4 = new RTest4();
            String name = "歪歪";
            rTest4.add(name);
            System.out.println("main方法的:" + name);
        }
    
    }
    

    输出结果为:

    add方法的:张三
    main方法的:歪歪
    

    原因是JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化,为字符串开辟一个字符串常量池,类似于缓存区(上面有介绍常量池)。所以并不会改变实际参数本身。

    案例二:为什么在方法体中添加代码user = new User();实际参数就不会被改变

    代码如下:

    package com.xyz.example;
    
    /**
     * @author 歪歪
     * @version 1.0
     * @date 2020/12/17 17:10
     */
    public class RTest5 {
    
        public void add(User user){
            user = new User();
            user.setName("张三");
            user.setAge(19);
            System.out.println("add方法的User:"+user);
        }
    
        public static void main(String[] args) {
            RTest5 rTest5 = new RTest5();
            User waiwai = new User();
            waiwai.setName("歪歪");
            waiwai.setAge(18);
            rTest5.add(waiwai);
            System.out.println("main方法的User" + waiwai);
        }
    
    }
    

    输出结果为:

    add方法的User:User{name='张三', age=19}
    main方法的UserUser{name='歪歪', age=18}
    

    原因是最开始实际参数调用add方法时将堆的地址传递给了形式参数,形式参数User接受该地址,后来在方法体中user又重新引用了一个新的地址,即user重新在堆中开辟了一个内存空间,并赋值,所以这样并不会改变实际参数的值。

  • 总结值传递和引用传递的重点区别

    值传递 引用传递
    根本区别 会创建副本(Copy) 不创建副本
    传递结果 方法中无法改变原始对象 方法中可以改变原始对象

作者:歪歪

参考文章:为什么说Java中只有值传递Java 到底是值传递还是引用传递?

本文通过理解java是值传递和引用传递做的总结笔记,如果文章有bug非常欢迎各位少侠指正留言讨论,如果觉得文章对你有帮助麻烦点赞再走呗。

posted @ 2020-12-17 17:29  Java'sBestBlog  阅读(87)  评论(0编辑  收藏  举报