













提纲:
1、版权声明
2、前言
3、正文
4、结论
5、附注
6、参考资料
===========================================================================================================================
2、前言:
在所有一切之前,先让我们看一个例子,如果您能得出这个例子的正确答案,下面的内容您应该基本上领会了。当然,请注意,本文不光只是阐述Java方法的参数传递问题,站在一个统一的角度还包括Java的赋值操作。如果您有兴趣,不妨把它看完:)
例子1源码:Tester.java(摘改自:Java参数传递方式)
public static void main(String[] args) {
int primitive = 2;
changePrimitive(primitive);
System.out.println(”Step 1, primitive = ” + primitive); //Step 1
//问题1:请问,Step 1这里primitive的值是多少?
MyClass myClass = new MyClass(); // Step 2
System.out.println(”Step 3, myClass = ” + myClass); //Step 3
changeObject(myClass);
System.out.println(”Step 8, myClass = ” + myClass); //Step 8
System.out.println(”Step 9, myClass.i = ” + myClass.i); //Step 9
//问题2:请问,Step 8这里myClass指向的是执行方法changeObject(MyClass)之前的Step 3的那个对象,
//还是在方法changeObject(MyClass)中的Step 5新生成的那个对象?
//问题3:现在,Step 9中,myClass.i等于多少了?
}
public static void changePrimitive(int primitive) {
primitive = 3;
}
public static void changeObject(MyClass myClass) {
System.out.println(”Step 4, myClass = ” + myClass); //Step 4
myClass.i = 3;
myClass = new MyClass(); //Step 5
System.out.println(”Step 6, myClass = ” + myClass); //Step 6
System.out.println(”Step 7, myClass.i = ” + myClass.i); //Step 7
}
}
class MyClass {
int i = 0;
}
请认真想好,例子中3个问题您的答案是什么?
这里是标准答案:
问题1:primitive = 2
问题2:myClass仍然指向的是执行方法changeObject(MyClass)之前的Step 2的那个对象。
问题3:myClass.i = 3
您做对了吗(首先坦白,在写这篇文章之前,我得出了错误的答案:(,见:Java参数传递方式(转帖))?怎么,像我一样,没做对?那么您该好好读一下下面的内容了,读了之后还做不对的话就把我这篇帖子扔进垃圾堆吧。不过,扔之前能给我回复一下为什么我将感激不尽:)
3、正文:
关于Java的赋值和参数传递是按值(by value)进行的还是按引用(by reference)进行的,这个问题曾经迷惑了很多人,包括我。而且,我想,这个问题还将继续迷惑一些人,包括那些C++的高手。
在这里,我不准备用“按值(by value)”和“按引用(by value)”这样的术语来阐述这个问题。因为,从字面的理解来看,这样的术语在不同的人头脑里有不同的含义。我试图从Java数据(包括原始类型(primitive type)和对象(ojbect))在内存中的存储这个角度,用一个自创的“术语”来阐述我对这个问题的理解。这个术语就是:“堆栈区数据复制(Stack Data Copy,简称SDC)”。详细一点就是:在Java中,不管是赋值操作还是参数传递操作–针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址_进行复制。
像上面抠字眼的句子读起来比较费力,我在后面将用两个例子并结合一些示意图来阐述我对这个问题的理解。希望各位朋友帮助纠正错误。
1)、赋值操作:
例子2源码:(Assign.java)
public static void main(String[] args){
int i = 1;
Object o = new Object();
System.out.println(”i = ” + i + ” ; o = ” + o ); // Step 1 (示意图:3-1-1)
int j = i;
Object p = o;
System.out.println(”i = ” + i + ” ; j = ” + j + ” ; o = ” + o + ” ; p = ” + p); //Step 2 (示意图:3-1-2)
j++;
p = new Object();
System.out.println(”i = ” + i + ” ; j = ” + j + ” ; o = ” + o + ” ; p = ” + p); //Step 3 (示意图:3-1-3)
}
}
对上面例子的说明:
(1),Step 1中,整数i和对象o得到赋值。
示意图3-1-1

从示意图3-1-1中可以看出:整数i存储在堆栈区(Stack);对象o的引用存储在了堆栈区,但是对象o的值却存储在了内存堆中(Heap),对象o的引用存储了对象o的地址。
Step 1在我的机器上的一次输出结果:
i = 1 ; o = java.lang.Object@a90653
示意图3-1-2

从示意图3-1-2中可以看出:整数i的值复制给了整数j,整数j同样存储在堆栈区;存储在堆栈区的对象o的引用中存储的_对象o的值的地址C_复制给了对象p的引用,对象p的引用同样在堆栈区中。因为对象p的引用_得到了对象o的引用复制过来的_对象o的值的存储地址C,所以对象p的引用_和对象o的引用_都指向了在堆(heap)中的同一个对象,并且,这个对象的地址是地址C。
Step 2在我的机器上的一次输出结果:
i = 1 ; j = 1 ; o = java.lang.Object@a90653 ; p = java.lang.Object@a90653
(3),Step 3中,整数j的值加1,赋给了对象p新的对象值。
示意图3-1-3

从示意图3-1-3中可以看出:整数i的值不变,整数j的值加1变为2,整数在堆栈区中;新生成的对象的值存储在了堆(Heap)中,地址为F。新生成对象的值的地址F_存储在了堆栈区p的引用中,替换了原来存储在其中的地址C。于是,p的引用就指向了新生成的对象,这个新生成的对象的值的地址_是地址F。而整数i和对象o的(包括对象o的引用)没有改变也不曾有任何改变(除了初次赋值)。
Step 3在我的机器上的一次输出结果:
i = 1 ; j = 2 ; o = java.lang.Object@a90653 ; p = java.lang.Object@de6ced
至此,通过上面的例子及其示意图和说明,我得到一个结论:
在Java赋值操作中,针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。这就是术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”在Java赋值操作中的阐述。
2)、方法中的参数传递操作:
例子2源码:(PassParameter.java)
static void showMe(int pi, Object po){
System.out.println(”pi = ” + pi + ” ; po = ” + po); // Step 2 (示意图:3-2-2)
pi++;
po = new Object();
System.out.println(”pi = ” + pi + ” ; po = ” + po); // Step 3 (示意图:3-2-3)
}
public static void main(String[] args){
int i = 1;
Object o = new Object();
System.out.println(”i = ” + i + ” ; o = ” + o); // Step 1 (示意图:3-1-1)
showMe(i, o);
System.out.println(”i = ” + i + ” ; o = ” + o); // Step 4 (示意图:3-2-3)
}
}
对上面例子的说明:
(1),Step 1中,与上面Assign.java中的Step 1相同,略,下面重复其示意图3-1-1。
示意图3-1-1

i = 1 ; o = java.lang.Object@a90653
(2),Step 2中,与上面Assign.java中的Step 2类似,只是Assign.java中的整数j和对象p变成了这里的方法showMe()中的参数:整数pi和对象po。并且,由于这里是参数传递,把Assign.java示意图3-1-2中的“=”替换成PassParameter.java示意图3-2-2中的“<–”,以此表示是参数传递。据我的理解,它们是一回事。
示意图3-2-2

pi = 1 ; po = java.lang.Object@a90653
(3),Step 3和Step 4合并起来,见示意图3-2-3同样,与上面Assign.java中的Step 3类似。
示意图3-2-3

Step 3和Step 4在我的机器上的一次输出结果:
i = 1 ; o = java.lang.Object@a90653
至此,通过上面的例子及其示意图和说明,我得到一个结论:
在Java方法参数传递操作中,针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。这就是术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”在Java方法参数传递操作中的阐述。
4,结论
综上所述:在Java中,不管是赋值操作还是方法的参数传递操作–针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。
所以,据我的理解,术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”能够有助于理解在Java中进行赋值和传递参数的机制,能够有助于在一定程度上消除“传值”、“传引用”等语义上的多变性的负面影响,可以提出来供大家交流。
6,参考资料:
全文完
2006年11月22日午首稿
2006年11月24日下午第一次更新
2006年11月28日早晨第二次更新(局部小更新)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。