- A method cannot modify a parameter of primitive type (that is, numbers or boolean values)
- A method can change the state of an object parameter
- A method cannot make an object parameter refer to a new object
首先回顾一下在程序设计语言中有关将参数传递给方法 (或函数) 的一些专业术语。值调用 (call by value) 表示方法接收的是调用者提供的值。而引用调用 (call by reference) 表示方法接收的是调用者提供的变量地址。一个方法可以修改按引用调用所对应的变量值,而不能修改按值调用所对应的变量值 (a method can modify the value stored in a variable that is passed by reference but not in one that is passed by value ) 。"......调用" (call by) 是一个标准的计算机科学术语,它用来描述各种程序设计语言中方法参数的传递方式 (事实上,以前还有call by name,Algol程序设计语言是最古老的高级程序设计语言之一,它使用的就是这种参数调用方式。不过,对于今天,这种传递方式已经成为历史) 。
Java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别的是,方法不能修改传递给它的任何参数变量的内容。例如,考虑下面的调用:
double percent =10;
harry.raiseSalary(percent);
不必理睬这个方法的具体实现,在方法调用之后,percent的值还是10。
下面仔细地研究一下这种情况。假定一个方法试图将一个参数值增加至三倍:
public static void tripValue(double x) // doesn't work
{
x = 3 * x;
}
然后调用这个方法:
double percent = 10;
tripleVaule(percent);
可以看到,无论怎样,使用这个方法之后,percent的值还是10。下面看一下具体的执行过程:
1) x被初始化为percent值的一个拷贝 (也就是10)。2) x被乘以3后等于30.但是percent仍然是10。
3) 这个方法结束之后,参数变量x不再使用。
Figure 4-6 Modifying a numeric parameter has no lasting effect
然而,方法参数共有两种类型:
- 基本数据类型(数字、布尔值)
- 对象引用
读者已经看到,一个方法不可能修改一个基本数据类型的参数。而对象引用作为参数就不同了,可以很容易地利用下面这个方法实现将一个雇员的薪金提高两倍的操作:
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}
当调用
harry = new Employee(...);
tripeSalary(harry);
时,具体的执行过程为:
1) x被初始化为harry值的拷贝,这里是一个对象的引用。
2) raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200% 。
3) 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪资增至3倍的雇员对象
Figure 4-7 Modifying an object parameter has a lasting effect
读者已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用通一个对象 (The method gets a copy of the object reference, and both the original and the copy refer to the same object) 。
很多程序设计语言 (特别是,C++和Pascal) 提供了两种参数传递的方式: 值调用和引用调用。有些程序员 (甚至本书的作者) 认为Java程序设计对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
首先,编写一个交换两个雇员对象的方法:
public static void swap(Employee x,Employee y) // doesn't work
{
Employee temp = x;
x = y;
y = temp;
}
如果Java程序设计语言采用的是引用调用,那么这个方法就应该能够实现交换数据的效果:
Employee a = new Employee("Alice",...);
Employee b = new Employee(“Bob”,...);
swap(a,b);
// does a now refer to Bob, b to Alice?
但是,方法并没有改变存储在变量a和b中的对象引用。swap方法的参数x和y被初始化为两个对象的拷贝,这个方法交换的是这两个拷贝。
// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice
最终,白费力气。在方法结束时参数变量x和y被丢弃了。原来的变量a和b仍然引用这个方法调用之前所引用的对象。
Figure 4-8 Swapping object parameters has no lasting effect
这个过程说明,Java程序设计语言对对象采用的不是引用调用,实际上,对象引用进行的是值传递 (Instead, object references are passed by value) 。
下面总结一下在Java程序设计语言中,方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数 (即数值型和布尔型)(A method cannot modify a parameter of primitive type (that is, numbers or boolean values) )
- 一个方法可以改变一个对象参数的状态 (A method can change the state of an object parameter)
- 一个方法不能实现让对象参数引用一个新的对象 (A method cannot make an object parameter refer to a new object)
例4-4中的程序给出了相应的演示。在这个程序中,首先试图将一个值参数的值提高两倍,但没有成功:
Testing tripeValue:
Before: percent=10.0
End of method: x=30.0;
After: percent=10.0;
随后,成功地将一个雇员的薪金提高了两倍:
Testing tripleSalary:
Before: salary=50000.0
End of method: salary=150000.0
After: salary=150000.0
方法结束之后,harry引用的对象状态发生了改变。这是因为这个方法可以通过对象引用的拷贝修改所引用的对象状态。
最后,程序演示了swap方法的失败效果:
Testing swap:
Before: a=Alice
Before: b=Bob
End of method: x=Bob
End of method: y=Alice
After: a=Alice
After: b=Bob
C++注释: C++有值调用和引用调用。引用参数标有&符号。例如,可以轻松地实现 voidtripleValue(double& x) 方法或 void swap(Employee& x. Employee& y) 方法实现修改它们的引用参数的目的。
例4-4 ParamTest.java
/**
* This program demostrates parameter passing in Java,
* @version 1.00 2000-01-27
* @author Cay Horstmann
*/
public class ParamTest{
public static void main(String[] args){
/*
* Test 1: Methods can't modify numric parameters
*/
System.out.println("Testing tripValue:");
double percent = 10;
System.out.println("Before: percent=" + percent);
tripleValue(percent);
System.out.println("After: percent=" + percent);
/*
* Test 2: Methods can change the state of object parameters
*/
System.out.println("\nTesting tripleSalary:");
Employee harry = new Employee("Harry",50000);
System.out.println("Before: salary=" + harry.getSalary());
tripleSalary(harry);
System.out.println("After: salary=" + harry.getSalary());
/*
* Test 3: Methods can't attach new objects to object parameters
*/
System.out.println("\nTesting swap:");
Employee a = new Employee("Alice",70000);
Employee b = new Employee("Bob",60000);
System.out.println("Before: a=" + a.getName());
System.out.println("Before: b=" + b.getName());
swap(a,b);
System.out.println("After: a=" + a.getName());
System.out.println("After: b=" + b.getName());
}
public static void tripleValue(double x) //doesn't work
{
x = 3 * x;
System.out.println("End of method: x=" + x);
}
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
System.out.println("End of method: salary=" + x.getSalary());
}
public static void swap(Employee x, Employee y)
{
Employee temp = x;
x = y;
y = temp;
System.out.println("End of method: x=" + x.getName());
System.out.println("End of method: y=" + y.getName());
}
}
class Employee // simplified Employee class
{
public Employee(String n, double s)
{
name = n;
salary = s;
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
}
—— 声明:此文源自《JAVA 核心技术》第四章第五节 "方法参数"