java 参数调用,是值还是引用?
是很多人,困扰的问题,不光是新手,和老手,
我搞了 java 这么多年也是没有搞清楚,最近下了一番功夫,才了解清楚,
先看个例子,
class MyStringTest() {
public static void main(String[] args) {
String a = "init value";
System.out.println(a);
modifyString(a);
System.out.println(a);
}
private static void modifyString(String str) {
str = "modified value";
}
}
看这个code, 一般人认为,应该打印结果是,
init value
modified value,
但是,实际结果是,
init value
modified value,
从这个问题出发,我就想探究下,java 中究竟是传值,还是传引用,
这个问题,众说纷纭,有能让人明白的,有听了更不清楚的,
最后,我发现这个问题需要结合jvm 堆,栈的理解才能清楚,当然还有一些相关的概念要清楚,
你就应该明白这个问题的,比如,基本类型,引用类型。
1, 先说一说 JVM 的基本结构,
有 class loader, 执行引擎,运行时数据区,
2, 然后,类的加载和实例化流程,
类作为一个 .class 文件,被 class loader 加载,
然后,在被引用到的时候,
class loader, 将 .class 文件,加载进 jvm,
方法被放到方法区,
new 的 instance 放到 heap,
执行时的环境,放到 stack,
3, 方法调用,和栈的结构
然后呢,
引用对象,都是产生在 heap 上,因为大小,和生存期不固定,
基本对象,因为长度,生成期固定,所以生成在 stack,
而我们执行方法的时候,传递参数,都是 copy value,
例子1和分析,
比如,
Class TestMethod() {
theMethod(int theInt , Object theObj) {
theInt = 10;
theObj.change();
}
public static void main(String[] args) {
int n = 20;
Object obj = new Object();
theMethod(n, obj);
}
}
在调用方法 theMethod 之前,我们在栈中,有一个当前环境,
一个obj 引用到堆上的 object O.
当前栈中,存的是一个引用地址,
还有一个是 n, 他是在栈中,生成的,所以直接就是,指到的就是 10这个 value,
然后,调用theMethod 方法,将现在的环境压栈,
开一个新栈,在新栈中,生成
theInt, 和theObj,
他们的值从n, 和 obj copy 过来。
那么 n 指向的地方,存的值是20,
theObj, 指向的地方,是 heap 中的 object,
在执行 theInt = 10 后,
我们会在 栈中,找新地方,存一个 10, 然后,将 theInt 指过去,
老的n 指向的地方不变。
在执行 theObj.change() 后,
我们会到 堆中的 object 对象存储区去,做相应的改动。
因为,obj 也是指向同一个地方,
那么,在方法执行后,推出的时候,我们会 pop 一个栈,
回到原始栈,
这时,你看,
n 还是指向,10 的位置,
obj 还是指向,new object instance 位置,
而由于heap 中 object instance 部分数据改了,所以 obj 也改了,
所以,实际都是传值的,
对于基本类型是这样,
对于引用类型,由于中间有一层引用关系, 【栈 到 heap 的 引用】,
所以,看起来是传递引用了,
例子2和分析,
再看下面的例子,
Class TestMethod() {
theMethod(Object theObj) {
theObj = new Object("B");
theObj.change();
}
public static void main(String[] args) {
Object obj = new Object("A");
theMethod(obj);
}
}
那么返回后, obj 是A 还是B呢,
根据上面的分析,
栈中的 obj, 变量,存的值,指向 heap 中的 object A instance,
在调用方法的时候,在创建新栈的时候,
新 theObj 变量,我们会将 obj 变量值,复制过去,也就是,指向 heap 中的 object A,
但是,在执行
theObj = new Object("B");
后,
theObj 变量,就不再指向 heap 中的object A instance 了,而是指向新的 object B instance,
这是调用 change() 方法,做改动,
也只是针对 object B instance,
在方法退出的时候,我们 pop 一个栈,恢复到以前的环境,
obj 所指向的 heap 中的 object A instance ,没有任何改变。
以上,就是我的分析,
可见,网上的一些分析,没有考虑栈和堆,就事论事,总是让大家不明白,
这也从一个方面说明了这个问题,
写java code, 也是要明白jvm 的基本实现逻辑,否则,就会不明白。
JVM堆的结构。
值不可变类型,
同时,我们也要考虑到 String 类型的特殊性,
它的值是不变的,其他同样的 box 类型也是,
Boolean, Integer,
还有,
String str = "abc";
String str = new String("abc");
这些的区别,我们都应该清楚,
这样写起代码来,才能不犯错误。
网上发现的相关资料。
http://pengjiaheng.javaeye.com/blog/518623
http://www.javaeye.com/wiki/jvm/2905-JVM