在Java的说明书中,说明了Java是“按值传递(pass-by-value)”的,并不存在什么“按引用传递(pass-by-reference)”
但是我们在一些博客中,看到有说Java中既有“按值传递”,也有“按引用传递”,而且在我们的实际使用中,有些时候确实给我们感觉像是有时候在“按值传递”,有时候在“按引用传递”,这是为什么呢?
其实我们的疑惑都是因为概念混淆不清的原因,接下来我们就按着下面这些问题的顺序,来一步步搞清楚到底是怎么回事
-
什么是我们感觉的按值传递,什么是我们感觉的按引用传递?
-
为什么我们会感觉像是有时候在按值传递,有时候在按引用传递?
-
Java真正的传递是怎么回事?
在解决问题之前,我们先明确几个概念
指针(pointer)
当我们在定义一个变量或常量时,例 Integer a; 此处的a就是一个指针,它并不是一个实际的对象
对象(object)
当我们在实例化一个类时,在内存中会创建一个类的实例,即为对象,例 Integer a = new Integer(1); 在代码中我们并不能说哪一部分就是对象,new只是实例化的操作,我们会将实例化出的对象的地址,即引用,赋值给 a(即指针)
引用(inference)
当我们实例化出一个对象后,该对象在内存中的地址即为引用
什么是我们感觉的按值传递
在传递参数时,我们把 参数的数值 拷贝一份给被调用的方法,传递后就互不相关了(这仅仅是我们感觉的,是错误的,后续会说明)
什么是我们感觉的按引用传递
在传递参数时,我们把 参数的引用 传递给被调用的方法,后续的操作还是在操作这个参数(这仅仅是我们感觉的,是错误的,后续会说明)
什么我们会感觉像是有时候在按值传递,有时候在按引用传递
例1:
//例1
public static void main(String[] args){
int a = 1;
int[]arr = {1,2,3};
call(a,arr);
System.out.println(a); //实际结果 1
System.out.println(arr); //实际结果 {0,2,3}
}
public static void call(int a,int[] arr){
a = 0;
arr[0] = 0;
}
在这个例子中,我们感觉 变量a 是按值传递了,数组arr 是按引用传递了
例2:
//例2
class Dog{
public Dog(String name){
this.name = name;
}
private String name;
//省略getter/setter
}
class Test{
public static void main(String[] args){
Dog myDog = new Dog("我的狗");
call(myDog);
System.out.println(myDog.getName()); //实际结果 “别人的狗”
Dog myDog2 = new Dog("我的狗");
call2(myDog2);
System.out.println(myDog2.getName()); //实际结果 “我的狗”
}
public static void call(Dog dog){
dog.setName("别人的狗");
}
public static void call2(Dog dog){
dog = new Dog("别人的第二条狗");
dog.setName("别人的第三条狗");
}
}
以我们的感觉来看,call()方法是引用传递,call2()方法是按值传递
当真是这样吗?
Java真正的传递是怎么回事
首先,我们必须要正确地定义,什么是 按值传递?
此处传递的值,是指 引用的值,Java说明书中所表达的意思是,按“引用的值”传递
那么我们用这个定义去解释例2
在call()方法中,myDog的引用被传递,call()方法操作了myDog引用指向的对象,setName()操作的是 原对象
在call2()方法中,myDog2的引用被传递,call2()方法中重新new了一个对象出来,setName()操作的是 新生成的对象
这样,例二中产生的结果就可以被正确的解释和理解了,那么我们再去用这个定义去解释例1
a的引用被call()方法操作,将a的值进行改变
arr的引用被call()方法操作,将该arr[0]的值进行改变
我们会发现,a的值并没有改变,这个解释不通,那么我们就要来考虑Java内存模型的问题了
我们知道,在Java中,有两大种数据类型:基本数据类型和引用数据类型
基本数据类型:8种基础数据类型和字符串(在声明之后java就会立刻分配给他内存空间)
引用数据类型:即对象(在实例化之后才会分配内存空间)
在例1中,a是基本数据类型,当call()方法执行了 a=1 这个语句后,Java会立即给1分配内存空间,所以此时call()方法中的a实际指向的是 1 的内存地址,而已经不是原来的引用了
这样就能正确解释例1中的情况了
所以,我们最后可以知道,Java中所有的传递都是 按值传递(引用值),而所谓的 按引用传递,只是因为我们没有明白 值 的真正意思。
最后,本文参考自 stackOverflow的一个问题,附上该问题地址https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value