在《Java核心技术 卷一 基础知识》第4.5节中专门讲解了方法参数,里面有一句话:Java程序设计语言总是采用按值调用。整个一节都是围绕着一句话来进行讲解,内容比较抽象,后来在stackoverflow上面看到了相关内容的精彩讨论(Is Java “pass-by-reference” or “pass-by-value”?),在此做近一步整理。
首先先明确几个术语,基本数据类型和引用类型(对象引用)是方法中参数的两种类型,按值调用和按引用调用是方法中参数的两种传递方式。注意这里的几个术语是一般程序设计语言中的术语,并非特指Java。
毋庸置疑的是,在Java中,方法的参数类型有基本数据类型和对象引用。例如,foo(int a)的参数类型是基本数据类型,foo(String a)的参数类型是引用类型。下面分别对这两类情况进行说明。
基本数据类型作参数
针对于参数类型是基本数据类型的情况,代码如下:
public class Test {
public static void tripleValue(double x){
x = 3*x;
System.out.println("在方法tripleValue() 内部, x = " + x);//x=6.0
}
public static void main(String[] args){
double a = 2;
System.out.println("调用方法 tripleValue() 之前, a = " + a);// a = 2.0
tripleValue(a);
System.out.println("调用方法 tripleValue() 之后, a = " + a);// a = 2.0
}
}
输出
调用方法 tripleValue() 之前, a = 2.0
在方法tripleValue() 内部, x = 6.0
调用方法 tripleValue() 之后, a = 2.0
上面这个比较简单,输出结果在预料之中,借用这个简单的例子,通过绘制图形的方式,来表示Java虚拟机是样分配内存的。
首先 double a = 2; 声明类一个基本数据类型a并为它赋予了初值2, Java虚拟机在栈顶分配了一块8字节的内存存储数值2.0。
前面声明的double类型的变量a属于基本数据类型,tripleValue方法以按值调用的方式调用a,此时传入tripleValue方法中的是a的一个拷贝。
当tripleValue方法内部执行 x = 3*x; 语句时实际上仅仅对a的拷贝进行了乘3操作,而a本身的值没有发生改变。
对象引用作参数
先亮代码:
public class HelloWorld
{
public static void reName(String name){
name += "re";
}
public static void main(String[] args){
String oldName = "Bob";
System.out.println(oldName);
reName(oldName);
System.out.println(oldName);
}
}
输出:
Bob
Bob
与基础数据类型double不同,String属于引用类型,即变量存的是对象的地址。同样,上面代码main方法中声明oldName之后,Java虚拟机内存分配的情况可用下图表示。
方法reName调用oldName时,我们先不管它是按照什么方式调用,先看reName方法质执行的过程。当reName方法内部执行语句 name += "re"; 时,JVM将创建一个新的String对象"reBob",即相当于执行了语句name = new String("reName"), name指向"reName"。
此时,若reName是按引用调用"Bod"对象的引用oldName, 则oldName和name都将指向新的对象"reBod";若reName按值调用"Bod"对象的引用oldName, 则name和oldName将指向不同的对象。从执行结果中我们可以看到,执行reName之后再打印oldName,结果仍然是"Bob",这表明oldName指向的对象仍然为"Bob"。
故在Java中,对象引用作参数时,方法按值调用实参,即方法得到的是对象引用的一个拷贝。
所以说:“Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是方法不能修改传递给它的任何参数变量的内容”。