值传递1.docx

上传人:b****5 文档编号:8244702 上传时间:2023-01-30 格式:DOCX 页数:27 大小:27.80KB
下载 相关 举报
值传递1.docx_第1页
第1页 / 共27页
值传递1.docx_第2页
第2页 / 共27页
值传递1.docx_第3页
第3页 / 共27页
值传递1.docx_第4页
第4页 / 共27页
值传递1.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

值传递1.docx

《值传递1.docx》由会员分享,可在线阅读,更多相关《值传递1.docx(27页珍藏版)》请在冰豆网上搜索。

值传递1.docx

值传递1

Java应用程序中的按值传递语义收藏

在不同的java新闻组中,参数是传值还是传址一直是一个经常被争辩的话题。

误解的中心是以下两个事实:

1、对象是传引用的

2、参数是传值的

这两个能够同时成立吗?

一个字:

是!

在java中,你从来没有传递对象,你传递的仅仅是对象的引用!

一句话,java是传引用的。

然而,当你传递一个参数,那么只有一种参数传递机制:

传值!

通常,当程序员讨论传值和传引用时,他们是指语言的参数传递机制,c++同时支持这两种机制,因此,以前使用过c++的程序员开始好像不能确定的java是如何传参数的。

java语言为了事情变得简单只支持参数传值的机制。

java中的变量有两种类型:

引用类型和原始类型。

当他们被作为参数传递给方法时,他们都是传值的。

这是一个非常重要的差别,下面的代码范例将说明这一点。

在继续前,我们有必要定义一下传值和传引用。

传值意味着当参数被传递给一个方法或者函数时,方法或者函数接收到的是原始值的副本。

因此,如果方法或者函数修改了参数,受影响的只是副本,原始值保持不变。

关于java中的参数传递的混乱是因为很多java程序员是从c++转变过来的。

c++有引用和非引用类型的变量,并且分别是通过传引用和传值得。

java语言有原始类型和对象引用,那么,按照逻辑,java对于原始类型使用传值而对引用是传引用的,就像c++一样。

毕竟,你会想到如果你正在传递一个引用,那么它一定是传引用的。

这是一个很诱惑人的想法,但是是错误的!


在c++和java中,当函数的参数不是引用时,你传递的是值得副本(传值)。

但是对于引用类型就不同了。

在c++中,当参数是引用类型,你传递的是引用或者内存地址(传引用),而在java中,传递一个引用类型的参数的结果只是传递引用的副本(传值)而非引用自身。

这是一个非常重要的区别!

java不考虑参数的类型,一律传递参数的副本。


仍然不信?

如果java中是传引用,那么下面的范例中的swap方法将交换他们的参数。

因为是传值,因此这个方法不是像期望的那样正常工作。


classSwap

{

publicstaticvoidmain(Stringargs[])

{

Integera,b;

inti,j;

a=newInteger(10);

b=newInteger(50);

i=5;

j=9;

System.out.println("BeforeSwap,ais"+a);

System.out.println("BeforeSwap,bis"+b);

swap(a,b);

System.out.println("AfterSwapais"+a);

System.out.println("AfterSwapbis"+b);

System.out.println("BeforeSwapiis"+i);

System.out.println("BeforeSwapjis"+j);

swap(i,j);

System.out.println("AfterSwapiis"+i);

System.out.println("AfterSwapjis"+j);

}

publicstaticvoidswap(Integeria,Integerib)

{

Integertemp=ia;

ia=ib;

ib=temp;

}

publicstaticvoidswap(intli,intlj)

{

inttemp=li;

li=lj;

lj=temp;

}

}

上面程序的输出是:


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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1