📄 破除java神话之二:参数是传址的 .txt
字号:
破除java神话之二:参数是传址的
作者:Cherami <BR>
email:<A HREF="mailto:cherami@javaresearch.org">cherami@javaresearch.org</A><BR>
<HR>
在不同的java新闻组中,参数是传值还是传址一直是一个经常被争辩的话题。误解的中心是以下两个事实:<BR>
1、对象是传引用的<BR>
2、参数是传值的<BR>
这两个能够同时成立吗?一个字:是!在java中,你从来没有传递对象,你传递的仅仅是对象的引用!一句话,java是传引用的。然而,当你传递一个参数,那么只有一种参数传递机制:传值!<BR>
通常,当程序员讨论传值和传引用时,他们是指语言的参数传递机制,c++同时支持这两种机制,因此,以前使用过c++的程序员开始好像不能确定的java是如何传参数的。java语言为了事情变得简单只支持参数传值的机制。<BR>
java中的变量有两种类型:引用类型和原始类型。当他们被作为参数传递给方法时,他们都是传值的。这是一个非常重要的差别,下面的代码范例将说明这一点。<BR>
在继续前,我们有必要定义一下传值和传引用。传值意味着当参数被传递给一个方法或者函数时,方法或者函数接收到的是原始值的副本。因此,如果方法或者函数修改了参数,受影响的只是副本,原始值保持不变。<BR>
关于java中的参数传递的混乱是因为很多java程序员是从c++转变过来的。c++有引用和非引用类型的变量,并且分别是通过传引用和传值得。java语言有原始类型和对象引用,那么,按照逻辑,java对于原始类型使用传值而对引用是传引用的,就像c++一样。毕竟,你会想到如果你正在传递一个引用,那么它一定是传引用的。这是一个很诱惑人的想法,但是是错误的!<BR>
在c++和java中,当函数的参数不是引用时,你传递的是值得副本(传值)。但是对于引用类型就不同了。在c++中,当参数是引用类型,你传递的是引用或者内存地址(传引用),而在java中,传递一个引用类型的参数的结果只是传递引用的副本(传值)而非引用自身。这是一个非常重要的区别!java不考虑参数的类型,一律传递参数的副本。<BR>
仍然不信?如果java中是传引用,那么下面的范例中的swap方法将交换他们的参数。因为是传值,因此这个方法不是像期望的那样正常工作。<BR>
<pre>
class Swap
{
public static void main(String args[])
{
Integer a, b;
int i,j;
a = new Integer(10);
b = new Integer(50);
i = 5;
j = 9;
System.out.println("Before Swap, a is " + a);
System.out.println("Before Swap, b is " + b);
swap(a, b);
System.out.println("After Swap a is " + a);
System.out.println("After Swap b is " + b);
System.out.println("Before Swap i is " + i);
System.out.println("Before Swap j is " + j);
swap(i,j);
System.out.println("After Swap i is " + i);
System.out.println("After Swap j is " + j);
}
public static void swap(Integer ia, Integer ib)
{
Integer temp = ia;
ia = ib;
ib = temp;
}
public static void swap(int li, int lj)
{
int temp = li;
li = lj;
lj = temp;
}
}
</pre>
上面程序的输出是: <BR>
<PRE>
Before Swap, a is 10
Before Swap, b is 50
After Swap a is 10
After Swap b is 50
Before Swap i is 5
Before Swap j is 9
After Swap i is 5
After Swap j is 9
</PRE>
因为swap方法接收到的是引用参数的副本(传值),对他们的修改不会反射到调用代码。<BR>
译者注:在传递引用和原始类型时还是有不同的,考虑以下的代码:
<pre>
class Change
{
public static void main(String args[])
{
StringBuffer a=new StringBuffer("ok");
int i;
i = 5;
System.out.println("Before change, a is " + a);
change(a);
System.out.println("After change a is " + a);
System.out.println("Before change i is " + i);
change(i);
System.out.println("After change i is " + i);
}
public static void change(StringBuffer ia)
{
ia.append(" ok?");
}
public static void change(int li)
{
li = 10;
}
}
</pre>
程序的输出为:<BR>
<PRE>
Before change, a is ok
After change a is ok ok?
Before change i is 5
After change i is 5
</PRE>
,即如果传递的是引用,那么可以修改引用对象的内容,这个改变会影响到原来的对象,而传递的如果是原始类型则不会有影响。这个也是造成误解的原因之一吧
//example 1
#include <iostream.h>
void exch(int *p1,int *p2);
int main()
{
int i =1,j=2;
cout<<"i="<<i<<",j="<<j<<endl;
exch(&i,&j);
cout<<"i="<<i<<",j<"<<j<<endl;
return 0;
}
void exch(int *p1,int *p2)
{
int* temp;
temp = p1;
p1 = p2;
p2 = temp;
}
//end example 1
结果会是:
i=1,j=2
i=1,j=2
同样不会发生任何变化,正确的写法应该是
//example 2
#include <iostream.h>
void exch(int *p1,int *p2);
int main()
{
int i =1,j=2;
cout<<"i="<<i<<",j="<<j<<endl;
exch(&i,&j);
cout<<"i="<<i<<",j<"<<j<<endl;
return 0;
}
void exch(int *p1,int *p2)
{int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;}
//end example 2
结果会是:
i=1,j=2
i=2,j=1
如上面代码说明的一样,其实无论何种语言传址方法都是一样的。传值和传址都是把内容复制给形参。只是传值复制得是变量的具体的值,而传址复制的是存放给变量的地址。你只是改变了形参里的地址,变量当然没有变化,你只有改变该地址里的值,才可以改变该变量的值。
C++里如果要改变两个地址,应该传的是个指向指针的指针。如下面的代码:
//example 2
#include <iostream.h>
void exch(int **p1,int **p2);
int main()
{ int a =1,b = 2;
int *i = &a;
int *j = &b;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*i="<<*i<<",*j="<<*j<<endl;
exch(&i,&j);
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*i="<<*i<<",*j=<"<<*j<<endl;
return 0}
void exch(int **p1,int **p2)
{int* temp;
temp = *p1;
*p1 = *p2;
*p2 = *temp;}
//end example 2
结果会是:
a=1,b=2
*i=1,*j=2
a=1,b=2
*i=2,*j=1
而Java中可以用这样的例子:
public class Test
{
public static void swap(Integer[] a)
{Integer temp= a[0];
a[0] = a[1];
a[1] = temp;}
public static void main(String[] args)
{Integer[] one = new Integer[2];
one[0] = new Integer(17);
one[1] = new Integer(3);
System.out.println("before swap");
System.out.println("one[0] is"+one[0]);
System.out.println("one[1] is"+one[1]);
swap(one);
System.out.println("after swap");
System.out.println("one[0] is"+one[0]);
System.out.println("one[1] is"+one[1]);}
}
结果是:
before swap
one[0] is 17
one[1] is 3
after swap
one[0] is 3
one[1] is 17
上面只是我个人的理解,不知道有没有错误,请大家批评指点。
c++里哪里有那么麻烦?
直接用&进行引用就行了
#include <iostream.h>
int swap(int& i,int& j);
void main()
{int i=1,j=2;
cout<<"i= "<<i<<" j= "<<j<<endl;
swap(i,j);
cout<<"i= "<<i<<" j= "<<j<<endl;}
int swap(int& i,int& j)
{int temp;
temp=i;
i=j;
j=temp;}
你的理解是错误的,java的参数是传址的,如果是传值的那么每一个对象应该实现clone方法
不然如何传递对象的拷贝,你举的例子正好使用的是不变类,如果你使用一个大的对象结果肯定不同的 什么是值传递?就是把变量复制一份吧?基本类型作为参数传递的时候,复制了一份,于是叫做值传递;传对象(的引用)的时候,也把它(对象的引用)复制了一份,只不过复制的是引用本身,因为Java里面所谓的对象都是指“对象的引用”,所以这也是“值传递”。
但是如果你以C/C++的观点来看,无疑会认为Java混淆了按引用传递的概念。
jackypeng:你的理解是错误的。因为Java的单根继承,所有对象都实现了clone方法。
如果希望传递对象的拷贝,应该显式调用clone方法。(是否需要override Object.clone(),那是出于“深”、“浅”的考虑)。不知道你所说的不变类是指哪个,Integer?
实际上一句话就说清楚了,为什么要写这么多来混淆概念呢?
Java中,一切对象作为参数时都是传引用,一切原始类型(int,float,...)都是传值。
I don't know how to type Simplified Chinese, so pardon me for using English.
I just want to state one point.
There are only 2 modes in parameter passing in Java functions.
One is for primitives, that is <<pass by value>> i.e. <<pass by copying>>.
The other is for Objects, that is <<pass reference by value>> i.e. java will copy the reference of an object and pass it to the function.
And that is why you can *never* write a swap function in Java as you can in C or C++. The common workaround to do swapping (as used extensive in JDK) is to put the object you want to swap in an array, and change the index of the array in the swap function.
public static void main(String[] args){
String a="aa", b="bb";
String[] data=new String[2];
data[0]=a;
data[1]=b;
swap(data);
a=data[0];
b=data[1];
//now a="bb"; and b="aa"
}
public static void swap(String[] data){
String temp;
temp=data[0];
data[0]=data[1];
data[1]=temp;
}
Please notice the subtle difference between C++'s pass by reference and Java's pass reference by value.
Hope this help.
I don't know how to type Simplified Chinese, so pardon me for using English.
I just want to state one point.
There are only 2 modes in parameter passing in Java functions.
One is for primitives, that is <<pass by value>> i.e. <<pass by copying>>.
The other is for Objects, that is <<pass reference by value>> i.e. java will copy the reference of an object and pass it to the function.
And that is why you can *never* write a swap function in Java as you can in C or C++. The common workaround to do swapping (as used extensive in JDK) is to put the object you want to swap in an array, and change the index of the array in the swap function.
public static void main(String[] args){
String a="aa", b="bb";
String[] data=new String[2];
data[0]=a;
data[1]=b;
swap(data);
a=data[0];
b=data[1];
//now a="bb"; and b="aa"
}
public static void swap(String[] data){
String temp;
temp=data[0];
data[0]=data[1];
data[1]=temp;
}
Please notice the subtle difference between C++'s pass by reference and Java's pass reference by value.
Hope this help.
class Change
{
public static void main(String args[])
{
String a=new String("ok");
System.out.println("Before change, a is " + a);
change(a);
System.out.println("After change a is " + a);
}
public static void change(String ia)
{
ia.concat("?");
}
}
可是输出的a没有任何变化,这就使我有点不懂了——不是说对象是传引用的吗,为什么您的例子成功了,但我却没有?我想这可能是两个方法的不同吧.这并不能推翻Cherami先生的说法.
java对象还是传引用的.
class Change
{
public static void main(String args[])
{
String a=new String("ok");
StringBuffer b=new StringBuffer("qi");
System.out.println("Before change, a is " + a);
System.out.println("Before change, b is " + b);
changea(a);
changeb(b);
System.out.println("After change, a is " + a);
System.out.println("After change, b is " + b);
}
public static void changea(String ia)
{
if(ia.concat("?").equals(ia))System.out.println("ia ok");
}
public static void changeb(StringBuffer ib)
{
if(ib.append("?").equals(ib))System.out.println("ib ok");
}
}
请看看我的专栏里面的另外两篇文章:
我对《Java 应用程序中的按值传递语义》的理解
Java 应用程序中的按值传递语义
以上两篇文章都在J2SE文章区。
sun2bin 说的没错在java 中基本类型直接采用值传递,就是把值复制一份,而对于任何其它复合类型传递的是对象的reference,相当于对象的地址。
函数调用时,每个函数都有它自己的参数区,对于基本类型直接把数据复制到参数区,而对于复合类型是把对象的reference复制到参数区。
看下面简单例子:
/**
*Test for parameter transfer
*
**/
public class PassParameter
{
/**
*Change the value of the arrary
*@para int data[], the Object of a int arrary
*
**/
public PassParameter(int data[])
{
int temp = data[1];
data[1] = data[0];
data[0] = temp;
}
public static void main(String[] args)
{
int[] temp = new int[2];
temp[0] = 0;
temp[1] = 1;
PassParameter passParameter1 = new PassParameter(temp);
System.out.print(temp[0]+" "+temp[1]);
}
}
我们可以发现外部对象temp 在经过PassParameter的初始化方法处理后,值就发生了变化,输出的将是:1 0
实际上C 与java 在参数的值或地址的传递问题上仔细想想,就会发现他们没有本质的区别,c中的简单类型也是值传递,复合类型传递的是存放指向对象或实体的地址的变量(该变量(地址)存放的是对象的地址),对于指针实际上是直接将对象的地址值直接复制到函数的参数区。
c与java的不同在于c中存在存放地址的变量(指针),这样就可以通过指针运算随意访问未定义的空间。java中不存在这样的存放地址的变量(指针),程序中的每个对象名称实际上都记录在一个变量表中,每个变量名对应一个reference No. 函数调用时传递的就是这个reference No。(系统再根据这个reference no 找到相关对象的地址(我的推测,系统中记录了每个对象的地址和reference No的对应关系),也因此我们就无法使用没定义过的对象)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -