★JAVA是值传递
值传递:会创建副本
引用传递:不会创建副本
值传递和引用传递的误区
错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
错误理解二:Java是引用传递。
错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法:
public static void main(String[] args)
这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。
在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
```java public static void main(String[] args) { ParamTest pt = new ParamTest(); String parameter = "Hollis"; //实际参数为 parameter pt.sout(parameter); }
//形式参数为 name public void sout(String name) { System.out.println(name); } ```
实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。
值传递(pass by value)是指在调用函数时将实际参数parameter复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
**引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。**
值传递:你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把新钥匙做什么都不会影响你手里的这把钥匙。
引用传递你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
钥匙就是栈内地址,房子就是堆中的对象。
但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。
那你说你会不会受到影响?
而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。
什么是形参?什么是实参?
```java package base03_值传递;
public class ValueTransfer {
private String name;
private int age;
public ValueTransfer(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "ValueTransfer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
String name1 = "a";
pass(name1);
System.out.println(name2);
ValueTransfer v = new ValueTransfer("a", 1);
pass(v);
System.out.println(v);
ValueTransfer v2 = new ValueTransfer("a", 1);
pass2(v2);
System.out.println(v2);
}
public static void pass(String name2) {
//相当于 new String("b");
//这个可以通过pass2验证
//和这个方法是同样的结果,不会影响实际参数。
//然后再把这个新创建的字符串的引用交给name。
//这个时候 main方法中name字符串指向的是 a
//这个时候 main方法中name字符串指向的是 b
name2 = "b";
//方法执行完毕name被回收
}
public static void pass(ValueTransfer v) {
v.name = "b";
v.age = 2;
}
public static void pass2(ValueTransfer v) {
v = new ValueTransfer("c", 3);
v.age=4;
}
} /* 结果: a ValueTransfer{name='b', age=2} ValueTransfer{name='a', age=1} */
/* 如果是值传递 假设name2的地址值是 0x123456 假设name1的地址值是 0x456789 那么形参和实参的地址值是不是指向了同一个对象? 答案:是,因为形参是实参地址的拷贝 形参和实参的地址值可能不一样,但是指向的对象是同1个。
对于方法pass(String name2) String类的存储是通过final修饰的char[]数组来存放结果的,不可更改。一旦定义就是最终形态,任何试图改变String值的操作都只能重新开辟地址。 故当外部一个String类型的引用name1传递到方法内部时,只是把“外部String实例对象”的引用传递给了方法参数变量name2,使得外部String类型变量name1和方法参数变量name2都是实际char[]数组的引用而已。 当我们在方法体中改变name2时,因为char[]数组是不可变的,故每次修改都会导致创建一个新的String实例对象,而方法体中的方法参数name2就会指向这个新创建的String实例对象,而非指向原来外部的String实例对象了。故从方法执行前到方法执行后,外部String类型的引用name1始终指向原String实例对象。
但是对于方法pass(ValueTransfer v),对象的引用也有2个,同时指向了同一个内存地址的堆内存里的对象,当堆内存中的数据发生改变, 也就意味着这2个引用指向的堆内存里的对象发生了改变。
但是对于方法pass2(ValueTransfer v),对象的引用也有2个,同时指向了同一个内存地址的堆内存里的对象,当执行代码: v = new ValueTransfer("c", 3); 意味着这2个引用指向的堆内存里的对象已经不是同1个对象了。 所以即使我们执行 v.age=4; 也不会影响到外部的实际参数对象。
*/ ```
```java package study;
public class User { String gender; String name;
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"gender='" + gender + '\'' +
", name='" + name + '\'' +
'}';
}
}
```
```java package study;
/* * 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 * 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 */ public class 值传递还是引用传递 { public static void main(String[] args) { 值传递还是引用传递 pt = new 值传递还是引用传递();
System.out.println(" ------------基本类型------------------ ");
int i =20;
System.out.println("print in main , i is " + i);
pt.pass(i);
System.out.println("print in main , i is " + i);
System.out.println(" -------------引用类型1----------------- ");
String name = "Hollis";
System.out.println("print in main , name is " + name);
pt.pass(name);
System.out.println("print in main , name is " + name);
System.out.println(" --------------引用类型2---------------- ");
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
System.out.println("print in main , hollis is " + hollis);
pt.pass(hollis);
System.out.println("print in main , hollis is " + hollis);
System.out.println(" --------------引用类型3---------------- ");
User hollis2 = new User();
hollis2.setName("Hollis");
hollis2.setGender("Male");
System.out.println("print in main , tyrant is " + hollis2);
pt.passAndNew(hollis2);
System.out.println("print in main , tyrant is " + hollis2);
System.out.println(" --------------引用类型4---------------- ");
User hollis3 = new User();
hollis3.setName("Hollis");
hollis3.setGender("Male");
System.out.println("print in main , tyrant is " + hollis3);
pt.pass(hollis3);
System.out.println("print in main , tyrant is " + hollis3);
}
public void pass(int j) {
j=100;
System.out.println("print in pass , j is " + j);
}
public void pass(String name) {
//因为String是不可变对象
//这里相当于new了一个String赋值给了形参
//形参和实参指向的不是一个对象
//修改不会影响实参属性
//可以理解为name这个形参 和 实参指向了同1个对象 a
//但是给name赋值的时候 ,由于String是不可变对象
//因此 name = ("hollischuang"); 相当于 name = new String("hollischuang");
//此时 name 相当于指向了 1个新对象 b
name = ("hollischuang");
System.out.println("print in pass , name is " + name);
}
public void passAndNew(User user) {
//这个和String一样
//形参 实参指向的不是同1个对象的引用
//不会影响实参的值
user = new User();
user.setName("hollischuang");
user.setGender("female");
System.out.println("print in pass , user is " + user);
}
public void pass(User user) {
//形参 实参指向1个对象的引用
//修改会影响实参属性
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
}
```
java ------------基本类型------------------ print in main , i is 20 print in pass , j is 100 print in main , i is 20 -------------引用类型1----------------- print in main , name is Hollis print in pass , name is hollischuang print in main , name is Hollis --------------引用类型2---------------- print in main , hollis is User{gender='Male', name='Hollis'} print in pass , user is User{gender='Male', name='hollischuang'} print in main , hollis is User{gender='Male', name='hollischuang'} --------------引用类型3---------------- print in main , tyrant is User{gender='Male', name='Hollis'} print in pass , user is User{gender='female', name='hollischuang'} print in main , tyrant is User{gender='Male', name='Hollis'} --------------引用类型4---------------- print in main , tyrant is User{gender='Male', name='Hollis'} print in pass , user is User{gender='Male', name='hollischuang'} print in main , tyrant is User{gender='Male', name='hollischuang'}
以引用类型2为例
在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。即堆内存中的内容。
所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。钥匙就是栈内地址,房子就是堆中的对象。
以引用类型1为例
既然这样,为啥上面同样是传递对象,引用类型1和引用类型2的结果不一样呢?即传递的String对象和User对象的表现结果不一样呢?
一开始形参和实参指向的是同一个对象。
String name = "Hollis";
在调用方法时,因为String是不可变对象
name = ("hollischuang");
这里相当于new了一个String赋值给了形参
这时形参和实参指向的不是一个对象,所以实参对象的值还是Hollis
我们在引用类型1中的pass方法中使用name = "hollischuang";
试着去更改name的值,阴差阳错的直接改变了形参name的引用的地址。
因为这段代码,会new一个String,在把引用交给name。
即name = "hollischuang";
等价于name = new String("hollischuang");
。
而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。
以引用类型3为例
稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456
(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址0x123456
交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即user = new User();
,会重新开辟一块0X456789
的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456
的内容(图3)。
上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有
通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。
所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
还有一个问题就是等号左边的内容是栈内存中的内容。也就是说 实参和形参指向了同一个内存的地址。
``` 在引用类型1中,String被重新赋值,相当于new String 赋值给了形参。但是实际参数的指针还是指向原来的String。所以经过pass方法不会改变。
在引用类型2中,User的name属性被重新赋值。实参和形参同时指向的是同一个内存的地址。因此影响到了实参的内容。
在引用类型3中,新创建了一个User。实参和形参同时指向的不是同一个内存的地址。因此影响不到实参的内容。 ```