java

发布于:2022-10-14 ⋅ 阅读:(277) ⋅ 点赞:(0)

1、引用传递

这一次,让你彻底明白Java的值传递和引用传递!_Java后端技术的博客-CSDN博客

值传递: 基本数据类型、String、Object都是值传递(看上面的地址解释的非常详细)

public static void main(String[] args) {
    Object o = new Object();
    System.out.println(o);
    change(o);
    System.out.println(o);
}

public static void change(Object o){
    o = null;
    System.out.println(o);
}

结果:

java.lang.Object@270421f5
null
java.lang.Object@270421f5

基本数据类型的局部变量:存储栈,也就是“虚拟机栈”中。

基本数据类型的成员变量:顾名思义,就是在类体中定义的变量。存储到了堆中

基本数据类型的静态变量:存储于方法区的运行时常量池中,静态变量随类加载而加载,随类消失而消失

引用传递:
”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响实参的真实内容。

对象,数组等是引用传递。传进来的形参是引用。

定义一个对象的过程:

Person per = new Person();

实际上有两个过程:

Person per; //定义变量
per = new Person();  //赋值

在执行Person per; 时,JVM先在虚拟机栈中的变量表中开辟一块内存存放per变量,在执行per=new Person()时,JVM会创建一个Person类的实例对象并在堆中开辟一块内存存储这个实例,同时把实例的地址值赋值给per变量。因此可见:
对于引用数据类型的对象/数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容。

举个例子:

先定义一个对象:

public class Person {
    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;
    }
}

写个函数测试一下:

    public static void PersonCrossTest(Person person) {
        System.out.println("传入的person的name:" + person.getName());
        person.setName("我是张小龙");
        System.out.println("方法内重新赋值后的name:" + person.getName());
    }

    @Test
    //测试
    public static void main(String[] args) {
        Person p = new Person();
        p.setName("我是马化腾");
        p.setAge(45);
        PersonCrossTest(p);
        System.out.println("方法执行后的name:" + p.getName());
    }

输出结果:

传入的person的name:我是马化腾
方法内重新赋值后的name:我是张小龙
方法执行后的name:我是张小龙

可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容。

*下面我们对上面的例子稍作修改,加上一行代码:

    public static void PersonCrossTest(Person person){
        System.out.println("传入的person的name:" + person.getName());
        person = new Person();//加多此行代码
        person.setName("我是张小龙");
        System.out.println("方法内重新赋值后的name:"+person.getName());
    }

输出结果:

 传入的person的name:我是马化腾
方法内重新赋值后的name:我是张小龙
方法执行后的name:我是马化腾

为什么这次的输出和上次的不一样了呢?

底层逻辑是:我们知道,对象和数组是存储在Java堆区的,而且堆区是共享的,因此程序执行到main()方法中的下列代码时

    Person p = new Person();
    p.setName("我是马化腾");
    p.setAge(45);
    PersonCrossTest(p);

JVM会在堆内开辟一块内存,用来存储p对象的所有内容,同时在main()方法所在线程的栈区中创建一个引用p存储堆区中p对象的真实地址,如图:

当执行到PersonCrossTest()方法时,因为方法内有这么一行代码:

person = new Person();

JVM需要在堆内另外开辟一块内存来存储new Person(),假如地址为“xo3333”,那此时形参person指向了这个地址,假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变

可以推出:实参也应该指向了新创建的person对象的地址,所以在执行PersonCrossTest()结束之后,最终输出的应该是后面创建的对象内容(张小龙)。

然而实际上,最终的输出结果却跟我们推测的不一样,最终输出的仍然是一开始创建的对象的内容。(马化腾)

由此可见:引用传递,在Java中并不存在。

但是有人会疑问:为什么第一个例子中,在方法内修改了形参的内容,会导致原始对象的内容发生改变呢?

这是因为:无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。


 

由图可以看出,方法内的形参person和实参p并无实质关联,它只是由p处copy了一份指向对象的地址,此时:

p和person都是指向同一个对象

因此在第一个例子中,对形参p的操作,会影响到实参对应的对象内容。而在第二个例子中,当执行到new Person()之后,JVM在堆内开辟一块空间存储新对象,并且把person改成指向新对象的地址,此时:

p依旧是指向旧的对象,person指向新对象的地址。

所以此时对person的操作,实际上是对新对象的操作,于实参p中对应的对象毫无关系

结语

因此可见:在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。
只是在传递过程中:

如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。

如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。

 

 

 

本文含有隐藏内容,请 开通VIP 后查看