锁定老帖子 主题:一个绝对害了不少人的Java技术问题!
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间: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:对于引用类型的参数来说,传递的是引用本身的拷贝. 所以关键要看你如何理解传值中的这个“值”了。 |
|
返回顶楼 | |
发表时间:2004-04-06
jaqwolf 写道 引用 Java里的传值实际上是拷贝引用,而不是拷贝对象。
OK,一语中第。 java传递是引用的拷贝,既不是引用本身,更不是对象。 所有的问题都结了。 由你的这句话就可以得出,Java中一切都是值传递。你可以通过方法来改变被引用的对象中的属性值,却无法改变这个对象引用(Object reference)本身.也就是当一个对象的实例被创建的时候,like this: Apple a = new Apple(); a 存的就是这个对象实例的地址。而这个地址,也就是a的值作为参数传到某个函数中的时候,a本身是不会改变的。 |
|
返回顶楼 | |
发表时间: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. |
|
返回顶楼 | |
发表时间:2004-04-06
没有想到这个问题引起这么多朋友的共鸣,
楼上有位仁兄关于“java传递是引用的拷贝,既不是引用本身,更不是对象。”这句话说得非常好!可惜现今发现的任何一本书都没有说明。 当然这个传递是对象,而不是简单数据类型,简单数据类型还是传递值的拷贝的。 楼上还有一位仁兄拿出了编译原理,这正是问题的所在,传递地址、传递引用、传递结果(也就是值拷贝),这里所说的传递引用和传递地址有什么差别? 楼上的liang_chen的理解非常好,我直到前一段才真正发现(在我身边差不多都不清楚这个问题),三年多的java分析设计开发经验,到今天才理解透这样一个基础的问题,我真是汗颜。:( 所以,其他还在争论不清楚的各位朋友,一定要认真地学习java,不能随便说自己精通的,哈哈,大家多加探讨,继续共同提高。 |
|
返回顶楼 | |
发表时间:2004-04-06
黄总,是不是我今天早上问你的问题阿?
|
|
返回顶楼 | |
发表时间: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企业技术”。 |
|
返回顶楼 | |
发表时间: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,为什么不能用? |
|
返回顶楼 | |
发表时间:2004-04-07
引用 不一定,只要make sence,为什么不能用?
嗬嗬,栈桢都被销毁了,还怎么make sence? |
|
返回顶楼 | |
发表时间:2004-04-07
哦,那是我理解错你的意思了。
我以为你说绝对不要在子方法里修改outside object呢。 |
|
返回顶楼 | |
发表时间:2004-04-07
哦,我说得不够清楚。我是针对最后一句t=t2说的。
|
|
返回顶楼 | |