几个月前developerWorks 发布了我的书 Practical Java 中的一些节选该书是由 AddisonWesley 出版的首先我将利用 developerWorks 上的此栏目回答读者提出的一些问题然后对有关这些节选的各种评论作一答复
节选理解参数是按值而不是按引用传递的说明 Java 应用程序有且仅有的一种参数传递机制即按值传递写它是为了揭穿普遍存在的一种神话即认为 Java 应用程序按引用传递参数以避免因依赖按引用传递这一行为而导致的常见编程错误
对此节选的某些反馈意见认为我把这一问题搞糊涂了或者将它完全搞错了许多不同意我的读者用 C++ 语言作为例子因此在此栏目中我将使用 C++ 和 Java 应用程序进一步阐明一些事实
要点
读完所有的评论以后问题终于明白了至少在一个主要问题上产生了混淆某些评论认为我的节选是错的因为对象是按引用传递的对象确实是按引用传递的节选与这没有沖突节选中说所有参数都是按值 另一个参数 传递的下面的说法是正确的在 Java 应用程序中永远不会传递对象而只传递对象引用因此是按引用传递对象但重要的是要区分参数是如何传递的这才是该节选的意图Java 应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数参数可以是对象引用而 Java 应用程序是按值传递对象引用的
C++ 和 Java 应用程序中的参数传递
Java 应用程序中的变量可以为以下两种类型之一引用类型或基本类型当作为参数传递给一个方法时处理这两种类型的方式是相同的两种类型都是按值传递的没有一种按引用传递这是一个重要特性正如随后的代码示例所示的那样
在继续讨论之前定义按值传递和按引用传递这两个术语是重要的按值传递意味着当将一个参数传递给一个函数时函数接收的是原始值的一个副本因此如果函数修改了该参数仅改变副本而原始值保持不变按引用传递意味着当将一个参数传递给一个函数时函数接收的是原始值的内存地址而不是值的副本因此如果函数修改了该参数调用代码中的原始值也随之改变
关于 Java 应用程序中参数传递的某些混淆源于这样一个事实许多程序员都是从 C++ 编程转向 Java 编程的C++ 既包含非引用类型又包含引用类型并分别按值和按引用传递它们Java 编程语言有基本类型和对象引用因此认为 Java 应用程序像 C++ 那样对基本类型使用按值传递而对引用使用按引用传递是符合逻辑的毕竟您会这么想如果正在传递一个引用则它一定是按引用传递的很容易就会相信这一点实际上有一段时间我也相信是这样但这不正确
在 C++ 和 Java 应用程序中当传递给函数的参数不是引用时传递的都是该值的一个副本(按值传递)区别在于引用在 C++ 中当传递给函数的参数是引用时您传递的就是这个引用或者内存地址(按引用传递)在 Java 应用程序中当对象引用是传递给方法的一个参数时您传递的是该引用的一个副本(按值传递)而不是引用本身请注意调用方法的对象引用和副本都指向同一个对象这是一个重要区别Java 应用程序在传递不同类型的参数时其作法与 C++ 并无不同Java 应用程序按值传递所有参数这样就制作所有参数的副本而不管它们的类型
示例
我们将使用前面的定义和讨论分析一些示例首先考虑一段 C++ 代码C++ 语言同时使用按值传递和按引用传递的参数传递机制
清单 C++ 示例 #include
#include
void modify(int a int *P int &r);
int main (int argc char** argv)
{
int val ref;
int *pint;
val = ;
ref = ;
pint = (int*)malloc(sizeof(int));
*pint = ;
printf(val is %d\n val);
printf(pint is %d\n pint);
printf(*pint is %d\n *pint);
printf(ref is %d\n\n ref);
printf(calling modify\n);
//按值传递 val 和 pint按引用传递 ref
modify(val pint ref);
printf(returned from modify\n\n);
printf(val is %d\n val);
printf(pint is %d\n pint);
printf(*pint is %d\n *pint);
printf(ref is %d\n ref);
return ;
}
void modify(int a int *p int &r)
{
printf(in modify\n);
a = ;
*p = ;
p = ;
r = ;
printf(a is %d\n a);
printf(p is %d\n p);
printf(r is %d\n r);
}
这段代码的输出为
清单 C++ 代码的输出 val is
pint is
*pint is
ref is
calling modify
in modify
a is
p is
r is
returned from modify
val is
pint is
*pint is
ref is
这段代码声明了三个变量两个整型变量和一个指针变量设置了每个变量的初始值并将其打印出来同时打印出了指针值及其所指向的值然后将所有三个变量作为参数传递给 modify 函数前两个参数是按值传递的最后一个参数是按引用传递的modify 函数的函数原型表明最后一个参数要作为引用传递回想一下C++ 按值传递所有参数引用除外后者是按引用传递的
modify 函数更改了所有三个参数的值
将第一个参数设置为
将第二个参数所指向的值设置为 然后将第二个参数设置为
将第三个参数设置为
将新值打印出来然后函数返回当执行返回到 main 时再次打印出这三个参数的值以及指针所指向的值作为第一个和第二个参数传递的变量不受 modify 函数的影响因为它们是按值传递的但指针所指向的值改变了请注意与前两个参数不同作为最后一个参数传递的变量被 modify 函数改变了因为它是按引用传递的
现在考虑用 Java 语言编写的类似代码
清单 Java 应用程序 class Test
{
public static void main(String args[])
{
int val;
StringBuffer sb sb;
val = ;
sb = new StringBuffer(apples);
sb = new StringBuffer(pears);
Systemoutprintln(val is + val);
Systemoutprintln(sb is + sb);
Systemoutprintln(sb is + sb);
Systemoutprintln();
Systemoutprintln(calling modify);
//按值传递所有参数
modify(val sb sb);
Systemoutprintln(returned from modify);
Systemoutprintln();
Systemoutprintln(val is + val);
Systemoutprintln(sb is + sb);
Systemoutprintln(sb is + sb);
}
public static void modify(int a StringBuffer r
StringBuffer r)
{
Systemoutprintln(in modify);
a = ;
r = null; //
rappend( taste good);
Systemoutprintln(a is + a);
Systemoutprintln(r is + r);
Systemoutprintln(r is + r);
}
}
这段代码的输出为
清单 Java 应用程序的输出 val is
sb is apples
sb is pears
calling modify
in modify
a is
r is null
r is pears taste good
returned from modify
val is
sb is apples
sb is pears taste good