先说结论:Java 程序设计语言总是采用按值调用。无论基础数据类型还是引用数据类型。
以下开始证明:
首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
按值调用 (call by value) 表示方法接收的是调用者提供的值。
按引用调用 ( call by reference)表示方法接收的是调用者提供的变量地址。
先说基本数据类型。
基本数据类型
有C基础的人应该不难理解以下这个例子:
public static void addNum(int num){ //不起作用
num += 12;
}
public static void main(String[] args) {
int x = 10;
addNum(10);
System.out.println(x);
}
不必理睬这个方法的具体实现, 在方法调用之后, x的值还是 10。
我们来从内存解析角度来看这个问题:
在C语言中管此叫作用域,num只是拿到了x的值的拷贝,并不能代表x本身。所以对num的操作,都不会对x产生任何影响。
所以基本数据类型Java采用的是值传递机制(也可以叫按值调用)
引用数据类型
我们先看下面这段代码:
这是一段修改员工的工资的代码
public static void changeSalary(Emp x){
x.raiseSalary(200);
}
当调用它时:
Emp employee = new Emp();
changeSalary(employee);
这名员工的工资确实提高了200。
看到这里是不是一些读者以为引用数据类型采用的引用传递?
错!
我们来看内存解析:
我们都知道:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果是引用数据类型,此时赋值的是变量所保存的数据的地址。
也就是说栈空间中的"employee"和"x"保存的都是Emp的地址,这个过程依旧发生了值传递:employee把值(Emp的地址)拷贝了一份赋值给了x。
到此有点强买强卖的感觉了,因为上一段文字分析虽然解释得通,但是不一定是科学,不一定是真理。必须经过验证才能成为结论!
实践是检验真理的唯一标准
很多程序设计语言(特别是, C++ 和 Pascal) 提供了两种参数传递的方式:值调用和引用调用。有些程序员认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
-----《Java核心技术卷Ⅰ》
我们先写一个交换员工的方法:
public static void swapEmp(Emp a, Emp b){
Emp t = a;
a = b;
b = t;
}
我们在main中调用这个方法
Emp x = new Emp("Jack");
Emp y = new Emp("Edward");
swapEmp(x,y);
//现在x是Edward,y是Jack了吗?
如果Java对引用数据类型采用的按引用调用,那么这个方法应该能实现x和y的交换。但是,方法并没有改变存储在变量 x 和 y 中的对象引用。swap方法的两个参数 a,b被初始化为两个对象引用的拷贝,这个方法交换的是两个拷贝。
初始化时:
执行swap里面的代码之后:
最终,白费力气。在方法结束时参数变量 a 和 b 被丢弃了。原来的变量 x 和 y 仍然引用这个方法调用之前所引用的对象。
这个过程说明:Java 程序设计语言对对象采用的不是引用调用,实际上, 对象引用是按值传递的。
下面总结一下 Java 中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。