论坛首页 Java企业应用论坛

一个绝对害了不少人的Java技术问题!

浏览 92913 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-04-06  
1、首先弄清楚一个问题:Java有没有指针?

对于在C和C++里头曾经给我们带来欢乐同时也有无限痛苦的指针,很多人宁愿它再也不要出现在Java里头。然而事实上,Java是有指针的,Java中每个对象(除基本数据类型以外)的标识符都属于指针的一种。但它们的使用受到了严格的限制和防范,在<Thinking in Java>一书中称它们为句柄。

2、传递句柄
将句柄传递进入一个方法时,指向的仍然是相同的对象。
public class Testit {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public static void main(String[] args) {
    Testit a = new Testit();
    a.setName("a");
    Testit b = new Testit();
    b.setName("b");
    System.out.println("before swap: " + "a=" + a + " name: " + a.getName());
    swap(a,b);
  }

  private static void swap(Testit swap1, Testit swap2) {
    System.out.println("swaping: " + "a= " + swap1 + " name: " + swap1.getName());
    Testit temp;
    temp = swap1;
    swap1 = swap2;
    swap2 = temp;
  }

}

输出结果:
before swap: a=com.lib.Testit@16930e2 name: a
swaping:       a= com.lib.Testit@16930e2 name: a

3、一个句柄的传递会使调用者的对象发生意外的改变。
public class Testit {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public static void main(String[] args) {
    Testit a = new Testit();
    a.setName("a");
    Testit b = new Testit();
    b.setName("b");
    System.out.println("before swap: " + "a=" + a + " name: " + a.getName());
    swap(a,b);
    System.out.println("after swap: " + "a=" + a + " name: " + a.getName());
  }

  private static void swap(Testit swap1, Testit swap2) {
    Testit temp;
    temp = swap1;
    swap1 = swap2;
    swap2 = temp;
    swap1.setName("a's name");
    swap2.setName("b's name");
  }

}

输出结果:
before swap: a=com.lib.Testit@16930e2 name: a
after swap:   a=com.lib.Testit@16930e2 name: b's name

我们看到,a依旧是原来那个a,但name却不是原来那个name了!
在swap()方法中,swap1和swap2互相换过来了,这个时候swap2指向的是a,所以在setName()的时候改变的是a的name,而不是b的name。

为什么会这样呢?
liang_chen兄高见:Java里的传值实际上是拷贝引用,而不是拷贝对象。

总结:
1:对于值类型的参数来说,传递的是值的拷贝.
2:对于引用类型的参数来说,传递的是引用本身的拷贝.
所以关键要看你如何理解传值中的这个“值”了。
0 请登录后投票
   发表时间:2004-04-06  
jaqwolf 写道
引用
Java里的传值实际上是拷贝引用,而不是拷贝对象。


OK,一语中第。
java传递是引用拷贝,既不是引用本身,更不是对象。
所有的问题都结了。


由你的这句话就可以得出,Java中一切都是值传递。你可以通过方法来改变被引用的对象中的属性值,却无法改变这个对象引用(Object reference)本身.也就是当一个对象的实例被创建的时候,like this: Apple a = new Apple(); a 存的就是这个对象实例的地址。而这个地址,也就是a的值作为参数传到某个函数中的时候,a本身是不会改变的。
0 请登录后投票
   发表时间:2004-04-06  
Pass by value
This brings up the terminology issue, which always seems good for an argument. The term is “pass by value,” and the meaning depends on how you perceive the operation of the program. The general meaning is that you get a local copy of whatever you’re passing, but the real question is how you think about what you’re passing. When it comes to the meaning of “pass by value,” there are two fairly distinct camps:


Java passes everything by value. When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference. Ergo, everything is pass-by-value. Of course, the assumption is that you’re always thinking (and caring) that references are being passed, but it seems like the Java design has gone a long way toward allowing you to ignore (most of the time) that you’re working with a reference. That is, it seems to allow you to think of the reference as “the object,” since it implicitly dereferences it whenever you make a method call. [ Add Comment ]
Java passes primitives by value (no argument there), but objects are passed by reference. This is the world view that the reference is an alias for the object, so you don’t think about passing references, but instead say “I’m passing the object.” Since you don’t get a local copy of the object when you pass it into a method, objects are clearly not passed by value. There appears to be some support for this view within Sun, since one of the “reserved but not implemented” keywords was byvalue. (There’s no knowing, however, whether that keyword will ever see the light of day.) [ Add Comment ]
Having given both camps a good airing, and after saying “It depends on how you think of a reference,” I will attempt to sidestep the issue. In the end, it isn’t that important—what is important is that you understand that passing a reference allows the caller’s object to be changed unexpectedly.
0 请登录后投票
   发表时间:2004-04-06  
没有想到这个问题引起这么多朋友的共鸣,
   楼上有位仁兄关于“java传递是引用的拷贝,既不是引用本身,更不是对象。”这句话说得非常好!可惜现今发现的任何一本书都没有说明。
   当然这个传递是对象,而不是简单数据类型,简单数据类型还是传递值的拷贝的。
   楼上还有一位仁兄拿出了编译原理,这正是问题的所在,传递地址、传递引用、传递结果(也就是值拷贝),这里所说的传递引用和传递地址有什么差别?
   楼上的liang_chen的理解非常好,我直到前一段才真正发现(在我身边差不多都不清楚这个问题),三年多的java分析设计开发经验,到今天才理解透这样一个基础的问题,我真是汗颜。:(
   所以,其他还在争论不清楚的各位朋友,一定要认真地学习java,不能随便说自己精通的,哈哈,大家多加探讨,继续共同提高。
0 请登录后投票
   发表时间:2004-04-06  
黄总,是不是我今天早上问你的问题阿?
0 请登录后投票
   发表时间:2004-04-07  
Java方法调用的一切中心都是栈(Stack).
每一个方法调用都产生一个独立的栈桢(Stack Frame)。在方法调用开始的时候,会把操作数压栈,return的时候,如果有返回值,则把返回值压入调用者程序的栈。方法结束后其栈桢被销毁。

所谓的传值就是这样实现的。

进入call()方法后,t和t2都只是本地变量,t的引用值由调用者压栈。对t执行setName()显然仍然执行对原对象的调用。而最后t=t2是通过一次压栈和一次弹出完成的。但是请记住,这个栈桢在这一句后被立即销毁,不会对调用者的栈桢产生影响。

其实,不需要花费力气在书籍中精心捉摸别人遣词造句,任何有心人都可以自己验证这个过程,这完全是最基本的jdk的功能:

对于如下程序:
23:	public static void call(Test t); {
24:		Test t2 = new Test();;
25:		t2.setName("cba");;
26:		t.setName("abc");;
27:		t = t2 ;
28:	}
29:
30: 	public static void main(String[] arg); {
31:		Test obj = new Test();;
32:		call (obj); ;
33:		System.out.println("obj"+obj.getName(););;
34:	}


jdk1.4.2编译器产生的字节码如下:

public static void call(Test);;
  Code:
   Stack=2, Locals=2, Args_size=1
   0:   new     #3; //class Test
   3:   dup
   4:   invokespecial   #4; //Method "<init>":();V
   7:   astore_1
   8:   aload_1
   9:   ldc     #5; //String cba
   11:  invokevirtual   #6; //Method setName:(Ljava/lang/String;);V
   14:  aload_0
   15:  ldc     #7; //String abc
   17:  invokevirtual   #6; //Method setName:(Ljava/lang/String;);V
   20:  aload_1
   21:  astore_0
   22:  return
  LineNumberTable:
   line 24: 0
   line 25: 8
   line 26: 14
   line 27: 20
   line 28: 22
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      23      0    t       LTest;
   8      15      1    t2       LTest;

public static void main(java.lang.String[]);;
  Code:
   Stack=3, Locals=2, Args_size=1
   0:   new     #3; //class Test
   3:   dup
   4:   invokespecial   #4; //Method "<init>":();V
   7:   astore_1
   8:   aload_1
   9:   invokestatic    #8; //Method call:(LTest;);V
   12:  getstatic       #9; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  new     #10; //class StringBuffer
   18:  dup
   19:  invokespecial   #11; //Method java/lang/StringBuffer."<init>":();V
   22:  ldc     #12; //String obj
   24:  invokevirtual   #13; //Method java/lang/StringBuffer.append:(Ljava/lang/
String;);Ljava/lang/StringBuffer;
   27:  aload_1
   28:  invokevirtual   #14; //Method getName:();Ljava/lang/String;
   31:  invokevirtual   #13; //Method java/lang/StringBuffer.append:(Ljava/lang/
String;);Ljava/lang/StringBuffer;
   34:  invokevirtual   #15; //Method java/lang/StringBuffer.toString:();Ljava/la
ng/String;
   37:  invokevirtual   #16; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;);V
   40:  return
  LineNumberTable:
   line 31: 0
   line 32: 8
   line 33: 12
   line 34: 40
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      41      0    arg       [Ljava/lang/String;
   8      33      1    obj       LTest;



最后要批判一下,这种程序不应该在任何实际代码中出现。楼主看待Java,思维仍然停留在C的全局内存分配和“变量=指针”的思路上,所以拼命试图理解到底是传指针还是传引用。而实际上Java拜虚拟机所赐,他的内存已经可以严格隔离为常量池/静态方法区/堆栈区。在java中要牢记“变量=堆栈”。换句话说,被调用的方法唯一能够影响调用者变量(即堆栈)的机会,就是return 一个对象/值,其生成的字节码是ireturn或者areturn,包含有一次对调用者栈桢的压栈操作。

最后,这个问题不应该归为“Java企业技术”。
1 请登录后投票
   发表时间:2004-04-07  
引用
可惜现今发现的任何一本书都没有说明。


Thinking in Java 里就有:

引用
When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference


引用
最后要批判一下,这种程序不应该在任何实际代码中出现


不一定,只要make sence,为什么不能用?
0 请登录后投票
   发表时间:2004-04-07  
引用
不一定,只要make sence,为什么不能用?


嗬嗬,栈桢都被销毁了,还怎么make sence?
0 请登录后投票
   发表时间:2004-04-07  
哦,那是我理解错你的意思了。

我以为你说绝对不要在子方法里修改outside object呢。
0 请登录后投票
   发表时间:2004-04-07  
哦,我说得不够清楚。我是针对最后一句t=t2说的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics