JAVA到底是值传递还是引用传递?结论:值传递
- 1.形参和实参
- 2.值传递与引用传递
- 3.JAVA中的值传递
- 4.小结
Java到底是值传递还是引用传递呢?可能我们背过很多次,说java是值传递(当然,网上也有一些同学信誓旦旦的说java是引用传递,错误),但是当我突然再问你java为什么是值传递的?那可能就有点懵了。
1.形参和实参
JAVA是一种面向对象的编程语言,一个类中有属性和方法,我们这里重点说下方法的定义。
package com.donkey;
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 int changeAge(int age) {
age = age+1;
System.out.println("changeAge方法执行后的年龄:" + age);
return age;
}
public static void main(String[] args) {
Person person = new Person();
person.changeAge(10);
}
}
我们重点看一下changeAge(int age)这个方法,这是一个有参的方法,在main方法中调用changeAge方法,并且传递参数age =1;
说到这里,我要引出两个概念:形式参数(形参)和实际参数(实参);
形参:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数;
实参:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
如上面的例子中,changeAge(int age)这个方法中的age就是形参,而调用这个方法时传递的10就是实参。
即:实参是调用有参方法的时候真正传递的内容,而形参是用于接收实参内容的参数。
2.值传递与引用传递
通过上面,我们知道了形参和实参,那么在调用方法的时候,我们怎么传递的呢?或者说传递的是什么呢?这就引出了下面的概念:值传递和引用传递。
值传递:调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
基于上面的理论知识,我们看下面这段程序:
public static void main(String[] args) {
Person person = new Person();
int age = 20;
person.changeAge(age);
System.out.println("main方法中的年龄:" + age);
}
输出的结果为:
changeAge方法执行后的年龄:21
main方法中的年龄:20
从结果可知,changeAge方法并没有改变实际参数age的值,实参age还是等于20。所以我们得出结论:java是值传递。
但真实情况可能并不是这么简单,因为我们传递是一个基本数据类型,那如果是传一个引用类型的实参呢?那再看下面的小程序:
public String changeName(String name) {
name = "zhangsan";
System.out.println("changeName方法执行后的名字:" + name);
return name;
}
public static void main(String[] args) {
Person person = new Person();
String name ="lisi";
person.changeName(name);
System.out.println("main方法中的name:" + name);
}
输出结果如下:
changeName方法执行后的名字:zhangsan
main方法中的name:lisi
显然,changeName方法并没有改变实参name的值,main方法中的name还是lisi。所以:java是值传递。
可能你说,你这个实验的不对,要是传递一个对象,就不是这样的了,那么我们以user为例,再看下面的小程序:
public void changPerson(Person person) {
person.setAge(30);
person.setName("zhangfei");
System.out.println("changPerson方法执行后的person::" + person);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person();
person.setAge(45);
person.setName("gunayu");
person.changPerson(person);
System.out.println("main方法中的person:" + person);
}
执行结果如下:
changPerson方法执行后的person::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘zhangfei’, age=‘30’}
哦哦哦,这时候你可能会说,看看吧,person的内容改变了,这说明java在传递对象的时候是引用传递。那么这个结论正确么?如果这个结论不正确,那它错在哪里呢?
3.JAVA中的值传递
通过上面的三个小程序,我们得出来了不同的结论,这也是我当初怎么也无法理解这个概念的原因。其实上面的理论没有问题,只是我们实验的方法由问题。通过上面值传递和引用传递的概念,我们知道,这两个的概念的最核心的区别就是值传递过程中或不会重新复制一份副本出来。
值传递 | 引用传递 | |
核心差异 | 会创建副本(copy) | 不会创建副本 |
结果 | 函数中不能改变实际参数 | 函数中可以改变时间参数 |
我们再来举个比较形象的例子。
当你的同事想开你的车出差,然后你有一把车钥匙,你直接把这把车钥匙给了同事。假如这个同时给车钥匙加了一个钥匙套,那么当同事把车钥匙还给你的时候,这把钥匙就会包裹了一个钥匙套,这就是引用传递。
当你的同事想开你的车出差,你有一把车钥匙,然后你找4S店复制了一把车钥匙,你把新复制的这把车钥匙给了同事。假如这个同时给车钥匙加了一个钥匙套,那么当同事把车钥匙还给你的时候,你手里的这把原始钥匙依然没有钥匙套,这就是值传递。
下面我们再看看一个小程序:
public void changPerson1(Person person) {
person = new Person();
person.setAge(30);
person.setName("zhangfei");
System.out.println("changPerson1::" + person);
}
public static void main(String[] args) {
Person guanyu = new Person();
guanyu.setAge(45);
guanyu.setName("gunayu");
guanyu.changPerson1(guanyu);
System.out.println("main方法中的person:" + guanyu);
}
执行结果如下:
changPerson1::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘gunayu’, age=‘45’}
当我们在main中创建一个Person对象的时候,在堆中开辟一块内存,其中保存了name和age数据。然后guanyu持有该内存的地址0x00000001
当尝试调用changPerson1方法,并且guanyu作为实际参数传递给形式参数person的时候,会把这个地址0x00000001
交给person,这时,person也指向了这个地址
然后在changPerson1方法内对参数进行修改的时候,即persnotallow= new Person();,会重新开辟一块
0x00000002的内存,赋值给person。后面对user的任何修改都不会改变内存
0x00000001`的内容,
上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在执行persnotallow= new Person();的时候,实际参数的引用也应该改为指向0x00000001,但是实际上并没有。
所以,java在传递对象时,传递是堆中的那一小块内存区域,而并不是person这个引用的本身。
**所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。**在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像车钥匙和车的关系,车钥匙通过引用执行车,所以如果是引用传递的话,变化的应该是车钥匙而不是引用的那辆车。
其实也好理解,guanyu = new Person(),其实完全就可以把那小块对空间理解成guanyu所对应的值。
4.小结
无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。
按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
即:Java中的传递,是值传递,如果是引用类型这个值,实际上传递的是对象的引用(即栈与对之间的那条指向的线,每次传递时,其实是复制出了这样一条线,只不过线的起点换成了形参,终点没有变)。