开讲之前我们需要弄清楚以下概念
- 形参和实参
实参:实际参数,在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”,必须有确定的值
形参:形式参数,是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,不需要有确定的值
public static void main(String[] args){
String name = 'xiaotw';//实际参数
Paramter pt = new Paramter();
pt.getParam(name);
}
public void getParam(String str){//形参
}
- 值传递和引用传递
值传递:是指在调用函数时将实际参数复制一份传递到函数中,方法接收的实参值的copy,会创建副本
引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,方法接受的是实参所引用对象在堆中的地址,不会创建副本,修改形参会影响到实参
了解了这些概念之后我们就可以开始写案列了
1
public static void main(String[] args){
int i = 10;
int j =20;
sout(i,j);
System.out.println("a = " +i);
System.out.println("b = " +j);
}
public void sout(int i ,int j){
i = 30;
j = 40;
System.out.println("c = " +i);
System.out.println("d = " +j);
}
结果是:
a = 10
j = 20
c= 30
d = 40
案列2
public static void main(String[] args) {
Paramter pt = new Paramter();
User xiaotw= new User();
xiaotw.setName("Xiaotw");
xiaotw.setAge(21);
pt.sout(xiaotw);
System.out.println(" main user is " + xiaotw);}
public void sout(User user) {
user.setName("xiaotw-aj");
System.out.println("sout user is " + user);}
结果:
main user is User{name='Xiaotw', age=21}
sout user is User{name='xiaotw-aj', age=21}
通过以上两个案列,可能有些小伙伴便会得出一个结论:在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。
但是,大家再看看下面写个案列
案列3:
public class Person {
private String name;
// 省略构造函数、Getter&Setter方法
}
public static void main(String[] args) {
Person xiaoZhang = new Person("小张");
Person xiaoLi = new Person("小李");
swap(xiaoZhang, xiaoLi);
System.out.println("xiaoZhang:" + xiaoZhang.getName());
System.out.println("xiaoLi:" + xiaoLi.getName());
}
public static void swap(Person person1, Person person2) {
Person temp = person1;
person1 = person2;
person2 = temp;
System.out.println("person1:" + person1.getName());
System.out.println("person2:" + person2.getName());
}
结果:
person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李
大家肯定会想:两个引用类型的形参互换并没有影响实参啊!
swap 方法的参数 person1 和 person2 只是拷贝的实参 xiaoZhang 和 xiaoLi 的地址。因此, person1 和 person2 的互换只是拷贝的两个地址的互换罢了,并不会影响到实参 xiaoZhang 和 xiaoLi 。
为了打消大家的疑虑,我们来看看,我们真正的改变参数,看看会发生什么?
public static void main(String[] args) {
Paramts pt = new Paramts ();
Person xiaotw= new Person();
xiaotw.setName("xiaotw");
xiaotw.setAge(21);
pt.sout(xiaotw);
System.out.println(" main person is " + xiaotw);}
public void sout(Person person ) {
person = new Person ();
person .setName("xiaotw-aj");
person .setAge(24);
System.out.println("sout person is " + person );}
结果:
pass , person is Person {name='xiaotw-aj', age=24}
main , person is Person {name='xiaotw', age=21}
来老规矩,上图
当我们在main中创建一个Person对象的时候,在堆中开辟一块内存,其中保存了name和age等数据。然后xiaotw持有该内存的地址0x67890(图1)
当尝试调用sout方法,并且xiaotw作为实际参数传递给形式参数Person的时候,会把这个地址0x67890交给person,这时,person也指向了这个地址。(图2)
然后在sout方法内对参数进行修改的时候,即 person = new Person ();,会重新开辟一块0x67891的内存,赋值给person 。后面对person 的任何修改都不会改变内存0x67890的内容(图3)。
以上的传递如果是引用传递的话,在执行Person person = new Person ();的时候,实际参数的引用也应该改为指向0x67890,但是实际上并没有,所以不是引用传递。
通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。
在参数传递的过程中,实际参数的地址0X67890被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容,而不是地址。
所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化