免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 12303 | 回复: 23
打印 上一主题 下一主题

java经典问题:传值还是传引用 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-03-17 19:03 |只看该作者 |正序浏览
经典的问题,但却不容易弄懂,尤其对有c基础的java程序员来说,更容易引起混乱,这里我试图简单点描述。

“java函数是传值的,java函数传递的参数是对象的引用”

这两句话好像初听上去有些矛盾,但却是事实,因而引起很多初学者的混乱。在这里我试图据个简单的例子来说明java的这个特性,可能不全面,希望大家来补全。


  1. public class TestRef {
  2.        
  3.         public static void main(String[] args)
  4.         {
  5.                 ValueObject vo1 = new ValueObject("A", 1);
  6.                 System.out.println("after vo1: " + vo1.getName()); //=A
  7.                
  8.                 changeValue1(vo1);
  9.                 System.out.println("after changeValue1: " + vo1.getName());
  10.                 //=A1, changed
  11.                
  12.                 changeValue2(vo1);
  13.                 System.out.println("after changeValue2: " + vo1.getName());
  14.                 //=A1, changeValue2内部的赋值不会影响这里。
  15.         }

  16.         /**
  17.          * 使用vo1自身的函数对其内部数据进行改变是有效的,函数外可反映出来
  18.          * 这种object称为可变的(mutable)
  19.          * @param vo1
  20.          */
  21.         private static void changeValue1(ValueObject vo1) {
  22.                 vo1.setName("A1");
  23.         }

  24.         /**
  25.          * 在函数内给vo1重新赋值不会改变函数外的原始值
  26.          * @param vo1
  27.          */
  28.         private static void changeValue2(ValueObject vo1) {
  29.                 vo1 = new ValueObject("B", 2);
  30.                 System.out.println("inside changeValue2: "+ vo1.getName());
  31.                 //=B,赋值操作引起的结果变化仅在changeValue2内部有效
  32.         }
  33. }

  34. class ValueObject {
  35.        
  36.         public ValueObject() {}
  37.        
  38.         public ValueObject(String name, int id)
  39.         {
  40.                 this.name = name;
  41.                 this.id = id;
  42.         }
  43.        
  44.         private String name;
  45.         private int id;
  46.         public int getId() {
  47.                 return id;
  48.         }
  49.         public void setId(int id) {
  50.                 this.id = id;
  51.         }
  52.         public String getName() {
  53.                 return name;
  54.         }
  55.         public void setName(String name) {
  56.                 this.name = name;
  57.         }
  58. }
复制代码


解释,vo1作为一个object,当它被用作函数参数的时候传递给函数的是一个引用值,这个名称有点怪,又有引用又有值,到底是引用还是值呢,就看你怎么理解了。如果你是去考试,官方的答案是值。可是看起来又象引用啊,希望从这个例子,你能理解java参数传递和和C/C++程序中的引用传递的不同的地方。另外,这也是java作为OO语言的特性之一:封装的体现。

先讲一下对象赋值的关系,举例来说,下列代码:

ValueObject v2, v3;
v2 = new ValueObject("C", 3); 粗体的部分创建了一个数据结构,假设存放在内存地址A000,赋值给句柄 v2
v3 = new ValueObject("D", 4); 粗体的部分创建了一个数据结构,假设存放在内存地址B000,赋值给句柄 v3
v2 = v3; 这句话的作用是把操作B000的地址的句柄的值付给了v2的句柄,使得v2和v3一样操作B000的地址,这意味着:
1.原来v2指向的地址A000变成无主的内存地址,将自动被jvm回收。
2.既然v2和v3指向同一片地址,对v3的修改v2也能得到,反之亦然。

整理得下列代码,请感兴趣的朋友运行验证
ValueObject v2 = new ValueObject("C", 3);
ValueObject v3 = new ValueObject("D", 4);
v2 = v3;
System.out.println("after v2=v3");
System.out.println("v2= "+ v2.getName());//=D
System.out.println("v3= "+ v3.getName());//=D
v3.setName("C1");
System.out.println("after v3 setnameTo C1");
System.out.println("vo2= "+ v2.getName());//=C1
System.out.println("vo3= "+ v3.getName());//=C1

因此,可以得出结论,java中对象的每个实例(instance, 比如vo1, v2, v3 都是ValueObject的实例)的内存地址是唯一的,它一旦被创建,能够对这个地址进行操作的就是每个实例自己,如果ValueObject类中没有public void setName之类的方法对这个类的实例中的数据进行修改的话,程序是没有任何别的方法可以修改ValueObject类的实例中的数据,这个就是java的封装特性。对于不提供修改内部数据的方法的类,我们称为不可变(immutable)的类。在函数中对传入的参数变量进行赋值操作,只能在函数范围内改变局部变量指向的引用地址,但是不会改变原始地址的内容。因此,在changeValue2(...)函数内部的vo1和函数外的vo1虽然名字相同,但是实际上是不同的实例变量,只不过指向了和函数外的vo1同样的地址,所以当我们用vo1=... 对其进行赋值的时候,只不过是把函数内的临时变量指向了新的地址,并没有改变原始vo1内存地址中的内容。这就是在运行changeValue2(...)之后,vo1的值在main范围内仍然没有被修改的原因。而changeValue1里面是调用的ValueObject本身的function来更改其内容,因此是原始内存地址中的数据被更改了,所以是全局有效的。

[ 本帖最后由 perryhg 于 2006-3-18 00:50 编辑 ]

论坛徽章:
0
24 [报告]
发表于 2006-04-01 16:56 |只看该作者
越说越糊涂,我都糊涂了

在搞不清楚的时候,请自己测试一遍

[ 本帖最后由 ayouxxx 于 2006-4-1 20:04 编辑 ]

论坛徽章:
0
23 [报告]
发表于 2006-03-30 13:18 |只看该作者
一句话嘛....就是传值....
无他

论坛徽章:
0
22 [报告]
发表于 2006-03-23 17:30 |只看该作者
原帖由 艾斯尼勒 于 2006-3-23 15:43 发表
是的。就是这个意思,我那段代码要说也就是参数传递都是by value的
只不过你若传递给方法的是一个对象(的引用),那是这个引用被复制了一份传递进去给方法
而方法获取了这个对象的引用可以去操作这个这对象。从 ...


你这个例子还是很好的,让学习JAVA的初学者知道参数传递和使用的机制,是个好例子

论坛徽章:
0
21 [报告]
发表于 2006-03-23 15:43 |只看该作者
是的。就是这个意思,我那段代码要说也就是参数传递都是by value的
只不过你若传递给方法的是一个对象(的引用),那是这个引用被复制了一份传递进去给方法
而方法获取了这个对象的引用可以去操作这个这对象。从语言本身的角度讲是传值,你不过你传进去的是一个引用罢了。若说是传引用的概念,应该是把这个引用的地址的传给了方法。那是C里面的指针才能操作的了

论坛徽章:
0
20 [报告]
发表于 2006-03-23 15:25 |只看该作者
我没有做试验,不过,我分析17楼的朋友的这个例子,输出的第一行和第三行的结果应该是一样的,
第二行就不同了,是因为:

虽然我在上面说过,传递的对象是不被复制的,这个是肯定的,但是传递的本质是指针值是要复制的。
它跟int char型的变量在这点上是没有区别的,不过要明白的是println(a)打印的是对象的地址,也就是指针的值,而不是对象本身。在change方法里的a的值是可以通过a=new A()来改变其值的,这个时候由于上新建对象,当然这个对象的地址就不一样了,也就是说在这个方法的a的值就发生了改变。但在这个方法外,a的值是没有发生任何改变的,所以第一行和第三行的打印结果是一样的。

论坛徽章:
0
19 [报告]
发表于 2006-03-23 15:00 |只看该作者
其实,传的都是值,关键是看你是从哪个角度来看问题,对于JAVA面向对象编程的开发者来说,除了
基本类型以外,可以认为方法的对象参数传递的都是应用,方法只是在应用这个对象,并没有拷贝这样一个对象,所以从这个角度来说,传递的又不是值!

论坛徽章:
0
18 [报告]
发表于 2006-03-23 14:55 |只看该作者
其实,应该是你说的这种结果,你打印出来的是对象的地址,也就是指针的值,从这个角度来看当然是传值的,

论坛徽章:
0
17 [报告]
发表于 2006-03-23 14:32 |只看该作者
原帖由 xxjoyjn 于 2006-3-23 14:12 发表
两次println(a)结果应该不一样才对


我一开始少写了一行发上来了

现在是3行输出
main中调用方法前第一次,调用方法里面第2此,调用完第3次

挺能证明都是传值的

论坛徽章:
0
16 [报告]
发表于 2006-03-23 14:12 |只看该作者
两次println(a)结果应该不一样才对
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP