java是值传递还是引用传递?

java是值传递还是引用传递?

java到底是什么传递?这个问题我在网上看到过很多答案,一下子把我也整得有点懵,所以我在知乎发了一篇帖子,想咨询一下大佬们的看法,结果也是说什么的都有。最后我结合值传递和引用传递的概念还有真实的案例得出了结论:java是值传递

首先,我们先来明白两个概念:

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

引用传递:指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

如果还不太理解的话,我们可以引入一张图:

大家应该对实参和形参的概念应该不陌生,在上图中,num是一个实参,method函数中的a是一个形参,值传递认为:a实际上是num复制出来的,这样在函数中对a的值是不会影响num的值。引用传递认为:在进行函数调用的时候,是把num的地址直接传给a,这样a和num在内存中保存的其实是一个值,修改了a的值之后,num也会跟着改变。

接下来我们从基本数据类型和引用数据类型来分析。

基础数据类型

话不多说,我们直接上代码:

  public static void main(String[] args) {
    int num = 100;
    char str = 'a';
    method(num, str);
    System.out.println("num===>: " + num);
    System.out.println("str===>: " + str);
  }

  public static void method(int a, char b) {
    a = 1000;
    b = 'b';
    System.out.println("a===>: " + a);
    System.out.println("b===>: " + b);
  }
  /*运行结果:
  a===>: 1000
  b===>: b
  num===>: 100
  str===>: a*/

运行结果我们可以看出,在method方法中改变了形参的值,但是它并没有影响实参的值,符合值传递的概念。

其实这个时候我们已经可以看到基础数据类型是值传递了,但是为了更清晰的了解,我们还是来画图说明。

因为是基础类型,所以我们并不考虑堆和方法区的存储,我们可以看到变量是存储在栈中的,其中method内部是新建了两个值,它对值的修改只会修改局部变量,并不会影响main方法中的值。

引用数据类型

首先我们先定义一个Person类

public class Person {

  private String name;
  private int age;
  private String sex;
  // get set 以及构造方法省略
}

定义一个personOne对象

public static void main(String[] args) {
  Person personOne = new Person();
  personOne.setName("某云");
  personOne.setSex("男");
  personOne.setAge(18);
  System.out.println(personOne);
  System.out.println(personOne.getName());
  System.out.println(personOne.getSex());
  System.out.println(personOne.getAge());
  /*运行结果
  包名.Person@4554617c
  某云
  男
  18*/
}

运行结果中我们可以分析出来personOne的值实际上是 包名.Person@4554617c这是一个地址值。

我们先来看第一个例子:

public static void main(String[] args) {
  Person personOne = new Person();
  personOne.setName("某云");
  personOne.setSex("男");
  personOne.setAge(18);
  System.out.println(personOne);
  System.out.println(personOne.getName());
  System.out.println(personOne.getSex());
  System.out.println(personOne.getAge());
  method(personOne);
  System.out.println("===========");
  System.out.println(personOne);
  System.out.println(personOne.getName());
  System.out.println(personOne.getSex());
  System.out.println(personOne.getAge());
}

public static void method(Person methodPerson){
  methodPerson.setName("某腾");
  methodPerson.setSex("女");
  methodPerson.setAge(19);
}
/*
包名.Person@4554617c
某云
男
18
===========
包名.Person@4554617c
某腾
女
19
*/

从运行结果来看,personOne被改变了,然后这种现象就会被大家误以为是引用传递,System.out.println(personOne);但是大家看这一句的打印结果是没有发生改变的,也就是说personOne的值并没有被改变,它始终指向包名.Person@4554617c,下面我们画图来说明一下。

new的所有对象都是存储在堆里面,我们从最开始就看到了直接打印变量,他返回的是包名.Person@4554617c说明变量里存储的实际是地址值,进入method方法中的时候会复制出来一个变量methodPerson,我们通过复制出来的变量可以直接访问堆里面new出来的对象,并且更改其中的属性,看起来很像引用传递,但是大家不要急,我们来看最后一个例子。

我们将第一个例子中的代码稍微更改一下:

  public static void main(String[] args) {
    Person personOne = new Person();
    personOne.setName("某云");
    personOne.setSex("男");
    personOne.setAge(18);
    System.out.println(personOne);
    System.out.println(personOne.getName());
    System.out.println(personOne.getSex());
    System.out.println(personOne.getAge());
    method(personOne);
    System.out.println("===========");
    System.out.println(personOne);
    System.out.println(personOne.getName());
    System.out.println(personOne.getSex());
    System.out.println(personOne.getAge());
  }

  public static void method(Person methodPerson){
    Person personTwo = new Person();
    personTwo.setName("某腾");
    personTwo.setSex("女");
    personTwo.setAge(19);
    methodPerson = personTwo;
    System.out.println("methodPerson ===========");
    System.out.println(methodPerson);
    System.out.println(methodPerson.getName());
    System.out.println(methodPerson.getSex());
    System.out.println(methodPerson.getAge());
  }

大家先来猜测一下结果,personOne的值会被改变吗?

答案是不会。

/*
包名.Person@4554617c
某云
男
18
methodPerson ===========
包名.Person@74a14482
某腾
女
19
===========
包名.Person@4554617c
某云
男
18
*/

不知道大家猜对了没有,methodPerson = personTwo;我明明给methodPerson赋值了,但是它为什么没有像例子一那样改变main函数中personOne的值呢。

我们继续看图:

personOne的值是0x333,进入到方法之后用methodPerson接收,methodPerson的初始值也是0x333。

现在定义一个personTwo,它指向的是0x666,最后我们将personTwo赋值给methodPerson;

程序走到这methodPerson改变了,但是personOne的值并没有改变。

总结

最后总结一下,为什么例子一可以改变,因为java形参中所接收的是实参的copy,在方法中我们可以通过这个copy找到堆内存中存储的数据从而改变堆内存,所以你并没有改变personOne的值,你只能改变personOne的值对应的堆内存的值,就像是最后一个例子看到的那样,methodPerson只是personOne的副本,你对这个副本的所有更改并不会影响personOne。

最后再回顾一下概念:

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

引用传递:指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

如果是引用传递的话,我在最后一个例子中改变methodPerson的值,personOne是应该要随之变化的,但是事实是并没有变化。

再来看值传递,上述所有例子都可以说明,方法内部其实是将实参进行copy,不管是什么类型,所有的更改都是针对copy出来的副本的,所以证实:java所有的都是值传递。

posted @ 2020-08-25 13:14  ihyb  阅读(730)  评论(0)    收藏  举报