红联Linux门户
Linux帮助

java经典问题 传值还是传引用

发布时间:2006-07-26 08:33:38来源:红联作者:pkncoin
  经典的问题,但却不容易弄懂,尤其对有c基础的java程序员来说,更容易引起混乱,这里我试图简单点描述。

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

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


引用:
public class TestRef {



public static void main(String[] args)

{

ValueObject vo1 = new ValueObject("A", 1);

System.out.println("after vo1: " + vo1.getName()); //=A



changeValue1(vo1);

System.out.println("after changeValue1: " + vo1.getName());

//=A1, changed



changeValue2(vo1);

System.out.println("after changeValue2: " + vo1.getName());

//=A1, changeValue2内部的赋值不会影响这里。

}



/**

* 使用vo1自身的函数对其内部数据进行改变是有效的,函数外可反映出来

* 这种object称为可变的(mutable)

* @param vo1

*/

private static void changeValue1(ValueObject vo1) {

vo1.setName("A1");

}



/**

* 在函数内给vo1重新赋值不会改变函数外的原始值

* @param vo1

*/

private static void changeValue2(ValueObject vo1) {

vo1 = new ValueObject("B", 2);

System.out.println("inside changeValue2: "+ vo1.getName());

//=B,赋值操作引起的结果变化仅在changeValue2内部有效

}

}



class ValueObject {



public ValueObject() {}



public ValueObject(String name, int id)

{

this.name = name;

this.id = id;

}



private String name;

private int id;

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}




  解释,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来更改其内容,因此是原始内存地址中的数据被更改了,所以是全局有效的。
文章评论

共有 3 条评论

  1. 依依杨柳 于 2006-07-30 09:55:49发表:

    c#也是哦
    不错,受教了

  2. johnhoo 于 2006-07-26 14:04:24发表:

    不只是java存在这个问题吧

  3. johnhoo 于 2006-07-26 14:03:19发表:

    嗯。。。