最近在写程序的时候遇到了一个问题,先来看下面一个小例子:
public class Case1 { public static void increment(int x) { x++; } public static void AppendString1(String s) { s += "Java"; return; } public static void main(String[] args) { String test = new String("Hello"); System.out.println("原先的字符串是 : " + test); AppendString1(test); System.out.println("给字符串添加后缀后: " + test); int x = 5; System.out.println("x原来的值:" + x); increment(x); System.out.println("x加1后:" + x); }
}
以下是程序的输出结果:
原先的字符串是 : Hello 给字符串添加后缀后: Hello x原来的值:5 x加1后:5
显然,执行increment方法后x的值并不会改变,但是AppendString1方法也没有成功给“Hello”加上后缀,这是怎么回事呢?
首先来看看Java中的两种变量类型,基本变量类型和引用变量类型。每个变量实际上都代表一个存储值的内存位置,声明一个变量时,就是在告诉编译器这个变量存放什么类型的变量。对于基本类型来说,对应内存所存储的就是这个基本类型的一个值;对于引用类型来说,对于内存所存储的值是一个引用地址,这个引用地址是实际对象的存储地址,即引用指向实际对象,实际对象中保存内容。变量在内存中的存储内容如下图所示:
理解了这两种不同的变量类型,接下来是“=”操作。=是赋值操作,其他+=、-=、*= 和 /= 等操作符其实都包含了赋值操作这一步。而这个赋值操作实际上又包括两步:1.放弃原来的值或者引用;2.获得=号右边的值或引用。下面这条语句: ClassName objRef = new ClassName() , 实际上包含了声明一个对象引用型变量objRef、创建一个ClassName类的对象、将这个对象的引用赋给变量objRef三个步骤。
在方法调用的过程中,参数传递实际上就是一次赋值操作。在调用带参数的方法时,实参的值要传给形参,这个过程称为值传递(pass-by-value)。方法的形参实际上也是一个变量,如果形参是基本型变量,那么传递的是基本类型的值的一个拷贝;如果形参是引用型变量,那么传递的就是实参这个变量所引用的对象在堆中的地址值的拷贝。无论形参在方法中是否改变,提供传递值的实参变量都不受影响。因为形参在方法中是作为局部变量存在的,当方法调用结束返回到上层调用者的时候,如果不是作为返回值被返回,它就消失了。在上面的例子中,main方法中的test字符串和传入AppendString1方法的参数s字符串一开始都指向堆中的同一个字符串对象,如下左图所示,调用AppendString1方法后,由于String对象的不可变性(immutability),“+=”操作生成一个新的字符串对象,并把这个新的字符串对象的引用赋给s,而test变量存储的引用地址值仍没变,如下右图所示:
在上面的程序中,如果要改变test变量所引用的字符串对象的值,就要进行赋值操作,让方法返回一个String类型的新的引用型变量并且赋值给test。
此外,还有另外一个小例子,代码如下所示:
public class A { String ID; int cnt; public A() { ID = new String("I am A."); cnt = 0; } } public static void setID (A a) { a.ID = new String("I was modified.");; } public static void main(String[] args) { A case = new A(); System.out.println(case.ID); setID(case); System.out.println(case.ID); }
结果如下:
I am A.
I was modified.
在这个例子中,setID方法的参数a拷贝了引用型变量case指向的的A类对象的地址值,这时候a和case都指向同一个对象,并且在整个setID方法中,a的值都没有发生变化,因此这个方法的操作改变了这个对象中的内容,虽然方法调用结束后变量a消失了,但是case指向的对象的内容确实发生了变化,即ID被改变了,但是case存储的地址值仍然是不变的(因为程序中没有任何对它进行赋新值的操作)。
总结一下,Java调用方法时参数的传递实际上就是一个赋值操作,因此也有人说Java没有引用传递,不要和C++中的传引用传指针混淆了。