黄子涵

4.5 方法参数

按值调用和按引用调用

按值调用(call by value)表示方法接收的是调用者提供的值。而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。“按......调用”(call by)是一个标准的计算机科学术语,用来描述各种程序设计语言(不只是Java)中方法参数的传递方式(事实上,以前还有按名调用(call by
name),Algol程序设计语言是最古老的高级程序设计语言之一,它使用的就是这种参数传递方式。不过,这种传递方式已经成为历史)。

Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个副本。具体来讲,方法不能修改传递给它的任何参数变量的内容。

例如,考虑下面的调用:

double percent = 10;
harry.raiseSalary(percent);

不论这个方法具体如何实现,我们知道,在这个方法调用之后,percent的值还是10。

下面再仔细研究一下这种情况。假定一个方法试图将一个参数值增加至3倍:

public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
}

然后调用这个方法:

double percent = 10;
tripleValue(percent);

调用方法执行过程

不过,这样并不能起作用。调用这个方法之后,percent的值还是10。下面看一下具体的执行过程:

  1. x初始化为percent值的一个副本(也就是10)。
  2. x乘以3后等于30,但是percent仍然是10(如图(修改数值参数没有持久效果)所示)。
  3. 这个方法结束之后,参数变量x不再使用。

修改数值参数没有持久效果

image

两种类型的方法参数

然而,有两种类型的方法参数:

  • 基本数据类型(数字、布尔值)。
  • 对象引用。

你已经看到,一个方法不可能修改基本数据类型的参数,而对象引用作为参数就不同了,可以很容易地利用下面这个方法将一个员工的工资增至三倍:

public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}

当调用

harry = new Employee(. . .);
tripleSalary(harry);

时,具体的执行过程为:

  1. x初始化为harry值的一个副本,这里就是一个对象引用。
  2. raiseSalary方法应用于这个对象引用。xharry同时引用的那个Employee对象的工资提高了200%。
  3. 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个工资增至3倍的员工对象(如图(修改对象参数有持久效果)所示)。

修改对象参数有持久效果

image

可以看到,实现一个改变对象参数状态的方法是完全可以的,实际上也相当常见。理由很简单,方法得到的是对象引用的副本,原来的对象引用和这个副本都引用同一个对象。

很多程序设计语言(特别是C++和Pascal)提供了两种参数传递的方式:按值调用和按引用调用。有些程序员(甚至有些书的作者)认为Java程序设计语言对对象采用的是按引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以很有必要给出一个反例来详细地说明一下这个问题。

首先,编写一个交换两个Employee对象的方法:

public static void swap(Employee x, Employee y) // doesn't work
{
Employee temp = x;
x = y;
y = temp;
}

如果Java对对象采用的是按引用调用,那么这个方法就应该能够实现交换:

var a = new Employee("Alice", . . .);
var b = new Employee("Bob", . . .);
swap(a, b);
// does a now refer to Bob, b to Alice?

但是,这个方法并没有改变存储在变量ab中的对象引用。swap方法的参数xy被初始化为两个对象引用的副本,这个方法交换的是这两个副本。

// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice

最终,白费力气。在方法结束时参数变量xy被丢弃了。原来的变量ab仍然引用这个方法调用之前所引用的对象(如图(交换对象参数没有持久效果)所示)。

交换对象参数没有持久效果

image

这个过程说明:Java程序设计语言对对象采用的不是按引用调用,实际上,对象引用是按值传递的

Java中方法参数的使用

下面总结一下在Java中对方法参数能做什么和不能做什么:

  • 方法不能修改基本数据类型的参数(即数值型或布尔型)。
  • 方法可以改变对象参数的状态
  • 方法不能让一个对象参数引用一个新的对象。

程序清单4-4中的程序展示了这几点。在这个程序中,首先试图将一个数值参数的值增至三倍,但没有成功:

Testing tripleValue:
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

可以看出,参数变量xy交换了,但是变量ab没有受到影响。

程序示例

/*
* 功能:这个程序演示了Java中的参数传递。
* @版本:1.01
* @时间:2021-07-17
* @作者:黄子涵
*
*/
public class HuangZiHanTest
{
public static void main(String[] huangzihan_args)
{
/*测试一:方法不能修改数值参数*/
System.out.println("测试一:方法不能修改数值参数");
System.out.println("huangzihan_tripleValue()测试中:");
double huangzihan_percent = 10;
System.out.println("测试之前:huangzihan_percent=" + huangzihan_percent);
huangzihan_tripleValue(huangzihan_percent);
System.out.println("测试之后:huangzihan_percent()=" + huangzihan_percent);
System.out.println();
/*测试二:方法可以更改对象参数的状态*/
System.out.println("测试二:方法可以更改对象参数的状态");
System.out.println("huangzihan_tripleSalary()测试中:");
var huangzihan = new Huangzihan_Employee("huangzihan", 50000);
System.out.println("测试之前:huangzihan_salary()=" + huangzihan.huangzihan_getSalary());
huangzihan_tripleSalary(huangzihan);
System.out.println("测试之后:huangzihan_salary()=" + huangzihan.huangzihan_getSalary());
System.out.println();
/*测试三:方法无法将新对象附加到对象参数*/
System.out.println("测试三:方法无法将新对象附加到对象参数");
System.out.println("huangzihan_swap()测试中:");
var huangzihan_a = new Huangzihan_Employee("Huangzihan_Alice", 70000);
var huangzihan_b = new Huangzihan_Employee("Huangzihan_Bob", 60000);
System.out.println("测试之前:huangzihan_a=" + huangzihan_a.huangzihan_getName());
System.out.println("测试之前:huangzihan_b=" + huangzihan_b.huangzihan_getName());
huangzihan_swap(huangzihan_a, huangzihan_b);
System.out.println("测试之后:huangzihan_a=" + huangzihan_a.huangzihan_getName());
System.out.println("测试之后:huangzihan_b=" + huangzihan_b.huangzihan_getName());
System.out.println();
}
public static void huangzihan_tripleValue(double huangzihan_x)
{
huangzihan_x = 3* huangzihan_x;
System.out.println("方法结束:huangzihan_x=" + huangzihan_x);
}
public static void huangzihan_tripleSalary(Huangzihan_Employee huangzihan_x)
{
huangzihan_x.huangzihan_raiseSalary(200);
System.out.println("方法结束:huangzihan_salary=" + huangzihan_x.huangzihan_getSalary());
}
public static void huangzihan_swap(Huangzihan_Employee huangzihan_x, Huangzihan_Employee huangzihan_y)
{
Huangzihan_Employee huangzihan_temp = huangzihan_x;
huangzihan_x = huangzihan_y;
huangzihan_y = huangzihan_temp;
System.out.println("方法结束:huangzihan_x=" + huangzihan_x.huangzihan_getName());
System.out.println("方法结束:huangzihan_y=" + huangzihan_y.huangzihan_getName());
}
}
class Huangzihan_Employee
{
String huangzihan_name;
double huangzihan_salary;
public Huangzihan_Employee(String huangzihan_n, double huangzihan_s)
{
huangzihan_name = huangzihan_n;
huangzihan_salary = huangzihan_s;
}
public String huangzihan_getName()
{
return huangzihan_name;
}
public double huangzihan_getSalary()
{
return huangzihan_salary;
}
public void huangzihan_raiseSalary(double huangzihan_byPercent)
{
double huangzihan_raise = huangzihan_salary * huangzihan_byPercent / 100;
huangzihan_salary += huangzihan_raise;
}
}

运行结果

测试一:方法不能修改数值参数
huangzihan_tripleValue()测试中:
测试之前:huangzihan_percent=10.0
方法结束:huangzihan_x=30.0
测试之后:huangzihan_percent()=10.0
测试二:方法可以更改对象参数的状态
huangzihan_tripleSalary()测试中:
测试之前:huangzihan_salary()=50000.0
方法结束:huangzihan_salary=150000.0
测试之后:huangzihan_salary()=150000.0
测试三:方法无法将新对象附加到对象参数
huangzihan_swap()测试中:
测试之前:huangzihan_a=Huangzihan_Alice
测试之前:huangzihan_b=Huangzihan_Bob
方法结束:huangzihan_x=Huangzihan_Bob
方法结束:huangzihan_y=Huangzihan_Alice
测试之后:huangzihan_a=Huangzihan_Alice
测试之后:huangzihan_b=Huangzihan_Bob
posted @   黄子涵  阅读(65)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示