先下结论:在 Java 语言中,本质只有值传递,而无引用传递
值类型就是Java 中的 8 大基础数据类型
所谓的值类型指的是在赋值时,直接在栈中(Java 虚拟机栈)生成值的类型
引用类型是指除值类型之外的数据类型
所谓的引用类型是指,在初始化时将引用生成栈上,而值生成在堆上的这些数据类型
int age = 10;
String str = "hello";
如上图所示,age是基本类型,值就存在栈中的变量里
而str是引用类型,栈中的变量保存的只是堆中实际对象的地址,一般称这种变量为引用,引用指向实际对象,实际对象中保存内容
int age = 24;
String str = "world";
对于基本类型age,赋值运算符会直接改变变量的值,原来的值被覆盖掉
对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象(“hello” 字符串对象)不会被改变(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)
public class PassByValue {
private static int x=22;
public static void updateValue(int value){
value = 3 * value;
}
public static void main(String[] args) {
System.out.println("调用前x的值:"+x);
updateValue(x);
System.out.println("调用后x的值:"+x);
}
}
运行结果
调用前x的值:10
调用后x的值:10
执行流程
分析结论
当传递方法参数类型为基本数据类型(数字以及布尔值)时,一个方法是不可能修改一个基本数据类型的参数
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name=name;
this.age=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 class PassByReference {
private static User user=null;
public static void updateUser(User student){
student.setName("change");
student.setAge(28);
}
public static void main(String[] args) {
user = new User("start",18);
System.out.println("调用前user的值:"+user.toString());
updateUser(user);
System.out.println("调用后user的值:"+user.toString());
}
}
运行结果
调用前user的值:User [name=start, age=18]
调用后user的值:User [name=change, age=28]
很明显,user对象的值被改变了,也就是方法参数类型为引用类型的时候,引用类型对应的值将会被改变
执行流程
分析结论
当传递方法参数类型为引用数据类型时,一个方法将修改一个引用数据类型的参数所指向对象的值
分析到这里,我们会发现,明明从代码中看到,既有值传递又有引用传递啊,可惜上面的代码其实是有一定的误导性的,下面再举一个例子来说明Java中只有值传递而没有引用传递
public class PassByValue {
private static User user=null;
private static User stu=null;
/**
* 交换两个对象
* @param x
* @param y
*/
public static void swap(User x,User y){
User temp =x;
x=y;
y=temp;
}
public static void main(String[] args) {
user = new User("user",26);
stu = new User("stu",18);
System.out.println("调用前user的值:"+user.toString());
System.out.println("调用前stu的值:"+stu.toString());
swap(user,stu);
System.out.println("调用后user的值:"+user.toString());
System.out.println("调用后stu的值:"+stu.toString());
}
}
运行结果
调用前user的值:User [name=user, age=26]
调用前stu的值:User [name=stu, age=18]
调用后user的值:User [name=user, age=26]
调用后stu的值:User [name=stu, age=18]
看到这里,是不是又会有疑惑了?如果是按照引用传递来说的话,user和stu的值会被交换啊,但实际运行结果是没有交换
通过下面的流程图可以看出,交换的只是user和stu的拷贝值,swap方法结束后,x、y都被释放,原来的user和stu仍然引用之前的对象
x和y只是user和stu这两个引用的副本,并不是将user和stu这两个引用指向的对象传给了swap方法,并且对象的引用是在栈上存在的,引用副本的传递也就是值传递,而不是引用对象的传递,前面的之所以能够修改引用数据,是因为原始引用和引用副本均指向同一个对象,而对对象进行操作的时候,就是切切实实的改变了这个对象,即使方法结束,传入方法的引用的拷贝值被销毁,此时对象已经被改变,所以看起来像是符合引用传递的定义,但是并不是引用传递
在 Java 语言中只有值传递,主要是看这个值是什么,如果是简单值变量就拷贝了一份具体值,如果是引用变量就是拷贝了一份内存地址,方法传参时只会传递副本信息而非原内容。我们还知道了基础数据类型会直接生成到栈上,而对象或数组则会在栈和堆上都生成信息,并将栈上生成的引用,直接指向堆中生成的数据