呵呵,其实地球人都知道Java中只有值传递,没有引用传递(啥?你不知道?那你面试咋过的。。。),但是有没有深究一下这是为什么呢?
引用这玩意儿应该是Java中的概念,对应C和C++里面的指针。Java中的数据类型分为基本类型和引用类型,基本类型指的就是常见的byte、short、int、long、float、double、char、boolean八种基本类型,这八种类型的变量,存储的就是值本身,比如short a = 5; 那么内存中会分配一块空间,它的名字是a(这里只是为了理解的方便,内存空间块并没有名字。。),里面存放的数据就是二进制的5,其他几种类似。
除了基本类型,就是引用类型了。比如我们常见的String类型,就是引用类型。String c = "ffef"; 那么给变量c分配的内存空间里存放的就是"ffef"吗?No,too young too simple! 变量c中存放其实是一个地址值,这个地址值指向的是Java堆上一块空间。要想获取c真正的内容,首先获取里面存放的地址值,然后再访问这个地址代表的内存,才能得到"ffef"。
好了,以上只是前提,下面开始说值传递。所谓值传递,指的是调用java方法时,方法得到的只是参数的一个副本,并不是参数本身。看代码:
int b = 5;
int c = plus(b);
int plus(x) {
x = x + 1;
return x;
}
那么调用plus之后,b的值会变吗?不会,因为调用plus时,实际上是方法的栈变量x复制了b的值,plus并不直接操作b,这就是所谓的值传递。
那么怎么才算是引用传递呢?那就看个C或C++里面的例子:
int plus(int* x) {
*x = *x + 1;
return *x;
}
int b = 4;
int c = plus(&b);
这段代码执行完毕之后,b和c的值都会是5,因为传递给plus的是b的地址,也就是plus函数会直接操作b代表的内存空间本身。
现在再来看,如果Java中的方法的参数是一个引用类型,也就是Java中的“指针”,会发生上面的情况吗?
public class Apple {
private int weight;
public static Apple changeWeight(Apple x) {
x.weight += 10;
return x;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public static void main(String[] args) {
Apple a = new Apple();
a.setWeight(20);
Apple b = changeWeight(a);
System.out.println(a.getWeight());
System.out.println(b.getWeight());
}
打印结果是,二者都是30。表面上看,将a传递给方法之后,a被改变了,这就像在上面C语言的代码中指针传递的效果一样。然而,实际上呢?上面说过,存储在变量a中的值实际上是一个“地址”,这个地址指向了堆中的一块内存空间;将a传递给changeWeight方法之后,该方法栈变量x复制了a的值,即x中的地址值也指向堆中的同一块空间。所以该方法确实是在操作a所真正指向的对象,这个对象的值改变之后,a作为对象的引用自然跟着改变。
矛盾吗?No,问题的核心在于,a的值变了吗?No,a还是指向原来的对象,changeWeight方法操作的是对象而不是变量a本身!引用类型的变量说穿了只是对象的一个影子而已,如果方法能够直接改变本体,那么影子自然也会改变——但是影子仍然对应着原来的本体。
Java只有对象的“指针”,并把它存储到引用类型的变量中,但是没有变量的“指针”。在调用Java方法时,传递给它的只是变量的副本(数值),不是变量本身(即地址或指针,Java没有机制传递这玩意儿),故Java(在方法调用时)只有值传递,没有所谓的引用传递。