BeforeSwap,ais10
BeforeSwap,bis50
AfterSwapais10
AfterSwapbis50
BeforeSwapiis5
BeforeSwapjis9
AfterSwapiis5
AfterSwapjis9
因为swap方法接收到的是引用参数的副本(传值),对他们的修改不会反射到调用代码。
译者注:
在传递引用和原始类型时还是有不同的,考虑以下的代码:
classChange
{
publicstaticvoidmain(Stringargs[])
{
StringBuffera=newStringBuffer("ok");
inti;
i=5;
System.out.println("Beforechange,ais"+a);
change(a);
System.out.println("Afterchangeais"+a);
System.out.println("Beforechangeiis"+i);
change(i);
System.out.println("Afterchangeiis"+i);
}
publicstaticvoidchange(StringBufferia)
{
ia.append("ok?
");
}
publicstaticvoidchange(intli)
{
li=10;
}
}
程序的输出为:
Beforechange,aisok
Afterchangeaisokok?
Beforechangeiis5
Afterchangeiis5
,即如果传递的是引用,那么可以修改引用对象的内容,这个改变会影响到原来的对象,而传递的如果是原始类型则不会有影响。
这个也是造成误解的原因之一吧。
Java应用程序中的按值传递语义
节选理解参数是按值而不是按引用传递的说明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++语言同时使用按值传递和按引用传递的参数传递机制:
清单1:
C++示例
#include
#include
voidmodify(inta,int*P,int&r);
intmain(intargc,char**argv)
{
intval,ref;
int*pint;
val=10;
ref=50;
pint=(int*)malloc(sizeof(int));
*pint=15;
printf("valis%d\n",val);
printf("pintis%d\n",pint);
printf("*pintis%d\n",*pint);
printf("refis%d\n\n",ref);
printf("callingmodify\n");
//按值传递val和pint,按引用传递ref。
modify(val,pint,ref);
printf("returnedfrommodify\n\n");
printf("valis%d\n",val);
printf("pintis%d\n",pint);
printf("*pintis%d\n",*pint);
printf("refis%d\n",ref);
return0;
}
voidmodify(inta,int*p,int&r)
{
printf("inmodify...\n");
a=0;
*p=7;
p=0;
r=0;
printf("ais%d\n",a);
printf("pis%d\n",p);
printf("ris%d\n",r);
}
这段代码的输出为:
清单2:
C++代码的输出
valis10
pintis4262128
*pintis15
refis50
callingmodify
inmodify...
ais0
pis0
ris0
returnedfrommodify
valis10
pintis4262128
*pintis7
refis0
这段代码声明了三个变量:
两个整型变量和一个指针变量。
设置了每个变量的初始值并将其打印出来。
同时打印出了指针值及其所指向的值。
然后将所有三个变量作为参数传递给modify函数。
前两个参数是按值传递的,最后一个参数是按引用传递的。
modify函数的函数原型表明最后一个参数要作为引用传递。
回想一下,C++按值传递所有参数,引用除外,后者是按引用传递的。
modify函数更改了所有三个参数的值:
将第一个参数设置为0。
将第二个参数所指向的值设置为7,然后将第二个参数设置为0。
将第三个参数设置为0。
将新值打印出来,然后函数返回。
当执行返回到main时,再次打印出这三个参数的值以及指针所指向的值。
作为第一个和第二个参数传递的变量不受modify函数的影响,因为它们是按值传递的。
但指针所指向的值改变了。
请注意,与前两个参数不同,作为最后一个参数传递的变量被modify函数改变了,因为它是按引用传递的。
现在考虑用Java语言编写的类似代码:
清单3:
Java应用程序
classTest
{
publicstaticvoidmain(Stringargs[])
{
intval;
StringBuffersb1,sb2;
val=10;
sb1=newStringBuffer("apples");
sb2=newStringBuffer("pears");
System.out.println("valis"+val);
System.out.println("sb1is"+sb1);
System.out.println("sb2is"+sb2);
System.out.println("");
System.out.println("callingmodify");
//按值传递所有参数
modify(val,sb1,sb2);
System.out.println("returnedfrommodify");
System.out.println("");
System.out.println("valis"+val);
System.out.println("sb1is"+sb1);
System.out.println("sb2is"+sb2);
}
publicstaticvoidmodify(inta,StringBufferr1,
StringBufferr2)
{
System.out.println("inmodify...");
a=0;
r1=null;//1
r2.append("tastegood");
System.out.println("ais"+a);
System.out.println("r1is"+r1);
System.out.println("r2is"+r2);
}
}
这段代码的输出为:
清单4:
Java应用程序的输出
valis10
sb1isapples
sb2ispears
callingmodify
inmodify...
ais0
r1isnull
r2ispearstastegood
returnedfrommodify
valis10
sb1isapples
sb2ispearstastegood
这段代码声明了三个变量:
一个整型变量和两个对象引用。
设置了每个变量的初始值并将它们打印出来。
然后将所有三个变量作为参数传递给modify方法。
modify方法更改了所有三个参数的值:
将第一个参数(整数)设置为0。
将第一个对象引用r1设置为null。
保留第二个引用r2的值,但通过调用append方法更改它所引用的对象(这与前面的C++示例中对指针p的处理类似)。
当执行返回到main时,再次打印出这三个参数的值。
正如预期的那样,整型的val没有改变。
对象引用sb1也没有改变。
如果sb1是按引用传递的,正如许多人声称的那样,它将为null。
但是,因为Java编程语言按值传递所有参数,所以是将sb1的引用的一个副本传递给了modify方法。
当modify方法在//1位置将r1设置为null时,它只是对sb1的引用的一个副本进行了该操作,而不是像C++中那样对原始值进行操作。
另外请注意,第二个对象引用sb2打印出的是在modify方法中设置的新字符串。
即使modify中的变量r2只是引用sb2的一个副本,但它们指向同一个对象。
因此,对复制的引用所调用的方法更改的是同一个对象。
编写一个交换方法
假定我们知道参数是如何传递的,在C++中编写一个交换函数可以用不同的方式完成。
使用指针的交换函数类似以下代码,其中指针是按值传递的:
清单5:
使用指针的交换函数
#include
#include
voidswap(int*a,int*b);
intmain(intargc,char**argv)
{
intval1,val2;
val1=10;
val2=50;
swap(&val1,&val2);
return0;
}
voidswap(int*a,int*b)
{
inttemp=*b;
*b=*a;
*a=temp;
}
使用引用的交换函数类似以下代码,其中引用是按引用传递的:
清单6:
使用引用的交换函数
#include
#include
voidswap(int&a,int&b);
intmain(intargc,char**argv)
{
intval1,val2;
val1=10;
val2=50;
swap(val1,val2);
return0;
}
voidswap(int&a,int&b)
{
inttemp=b;
b=a;
a=temp;
}
两个C++代码示例都像所希望的那样交换了值。
如果Java应用程序使用“按引用传递”,则下面的交换方法应像C++示例一样正常工作:
清单7:
Java交换函数是否像C++中那样按引用传递参数
classSwap
{
publicstaticvoidmain(Stringargs[])
{
Integera,b;
a=newInteger(10);
b=newInteger(50);
System.out.println("beforeswap...");
System.out.println("ais"+a);
System.out.println("bis"+b);
swap(a,b);
System.out.println("afterswap...");
System.out.println("ais"+a);
System.out.println("bis"+b);
}
publicstaticvoidswap(Integera,Integerb)
{
Integertemp=a;
a=b;
b=temp;
}
}
因为Java应用程序按值传递所有参数,所以这段代码不会正常工作,其生成的输入如下所示:
清单8:
清单7的输出
beforeswap...
ais10
bis50
afterswap...
ais10
bis50
那么,在Java应用程序中如何编写一个方法来交换两个基本类型的值或两个对象引用的值呢?
因为Java应用程序按值传递所有的参数,所以您不能这样做。
要交换值,您必须用在方法调用外部用内联来完成。
结论
我在书中包括该信息的意图并不是作琐细的分析或试图使问题复杂化,而是想警告程序员:
在Java应用程序中假定“按引用传递”语义是危险的。
如果您在Java应用程序中假定“按引用传递”语义,您就可能写出类似上面的交换方法,然后疑惑它为什么不正常工作。
我必须承认,在我第一次认识到Java应用程序按值传递所有参数时,我也曾表示怀疑。
我曾一直假定因为Java应用程序有两种类型,所以他们按值传递基本类型而按引用传递引用,就像C++那样。
在转向Java编程之前我已用C++编程好几年了,感觉任何其他事情似乎都不直观。
但是,一旦我理解了发生的事情,我就相信Java语言按值传递所有参数的方法更加直观。
TheJavaProgrammingLanguage,SecondEdition的作者,KenArnold和JamesGosling在2.6.1节中说得最好:
“在Java中只有一种参数传递模式--按值传递--这有助于使事情保持简单。
”
更多技术文章,请访问我的BLOG:
解惑
版权声明本篇文章对您是否有帮助?
投票:
是否投票结果:
43
作者其它文章:
没落的Java社区
也谈Java基础的重要性(续)
也谈Java基础的重要性
Google面试题解说性能之总结
Google面试题解说性能之八:
工欲善其事必先利其器
作者全部文章查看作者的Blog
评论人:
ljumwzc发表时间:
SatAug2411:
07:
21CST2002
愚认为Java中是对象参数是按引用(地址)传递的,请看下例:
classaugust
{
inta;
StringBuffersb1;
august()
{
a=10;
sb1=newStringBuffer("你好,");
}
}
publicclasssaturday
{
publicstaticvoidmethod(augustarg1)
{
arg1.a+=3;
arg1.sb1.append("Java");
System.out.println(arg1.a);
System.out.println(arg1.sb1);
}
publicstaticvoidmain(Stringargs[])
{
august