Problem
用过java的童鞋可能都爬过java中引用传递还是值传递的坑。不磨迹,直接上代码,看结果。
public class RefVal{
public static void main(String[] args){
String st="haha";
change(st);
System.out.println(st);
}
public static void change(String s){
s="hehe";
}
}
如果在命令行下,shell中编译执行命令:
javac RefVal.java
java -cp . RefVal
输出的结果是原来的字符串的值,也即”haha”。表象像是change函数未修改s的内容。
网上看了看解释,说的多离谱的都有。有的说”java是引用传递,但是传递String时,由于String是“=”haha”“的形式,后者是一个常量,如果是在c++中,等同于一个常量char指针,被jvm修饰成了final,所以其值不会改变”,说的这么邪乎,还把深入jvm的一点东西旁征博引了,看了直摇头啊。
如果我们把对象的内存地址打印出来了,一切不都了然了吗?虽然java中不能直接打印内存地址,但是hashCode()方法也相当于对象的内存地址了。
修改代码如下:
public class RefVal{
public static void main(String[] args){
String st="haha";
System.out.println(st.hashCode());
change(st);
System.out.println(st);
}
public static void change(String s){
System.out.println(s.hashCode());
s="hehe";
System.out.println(s.hashCode());
}
}
输出结果如下:
3194802
3194802
3198650
haha
看明白没。也就是说不用调用”=”操作符号时,change函数中参量与外部的参量是同一个对象的。但是一旦使用了”=”操作符号,change中的对象就变成了与原来的对象不相同的一个对象。
假设开始时st指向的内容为C,在调用change()函数后,s是st的一个引用,也即指向同一个对象C。当对s调用了”=”操作符后,s指向了另外一个对象。
Solution
所以,正确的解释了就是java中传对象类型参数时,其实是创建了一个”新对象”的,只不过”新对象”与原来参数对象都是同一个对象,但是”新对象”还是会占用新开辟的内存区,即如果调用c++取地址打印的话,”新对象”与原来对象地址是不同的,但是它们都指向了同一个对象。如果在函数中调用”obj.func”的方式进行赋值操作,那么等同于原对象调用相应方法,会修改到传递进来的对象。但是如果对”新对象”调用了”=”赋值操作符后,”新对象”就真的变成了一个新对象,与原来传入的对象不相干了。
即未调用”=”前,st->C,s->C,
调用”=”后, st->C,s->D。
如果用c++中的对象指针值传递语义来解释就很好理解了。change函数等同于如下c++代码
void change(std::string* s){
std::string strNew="strNew";
s=&strNew;
};
怎么理解呢,其实就是java传递底层实现是传递一个对象的指针,而java底层就是c++实现的,c++中参数传递都是值传递语义,传递一个对象的指针,会新建一个临时的对象(也称匿名对象)指针(姑且称之为pT),pT按位复制原来的指针地址,也就导致pT与原指针指向同一个对象。java在函数内部用”=”来给对象赋值时,等同于c++中给一个对象指针赋值。也即相当于修改了pT指向的内容地址,而原来传入的参数指针地址是未变的。而对对象指针调用”=”赋值操作符,也就将c++中的临时对象指向了新的对象。
下面用实际的c++代码来验证下。
#include<cstdio>
#include<iostream>
void change(std::string* s){
std::cout<<"old s ="<<*s<<std::endl;
std::cout<<"s old addr="<<s<<std::endl;
std::string strNew="strNew";
s=&strNew;
std::cout<<"new s ="<<*s<<std::endl;
std::cout<<"s addr="<<s<<std::endl;
};
int main(){
std::string st="haha";
std::cout<<"st="<<st<<std::endl;
std::cout<<"st addr="<<&st<<std::endl;
change(&st);
std::cout<<"st="<<st<<std::endl;
std::cout<<"st addr="<<&st<<std::endl;
return 0;
};
亲,这段程序不会报错的哟。输出如下。
st=haha
st addr=0x7ffc12580690
old s =haha
s old addr=0x7ffc12580690
new s =strNew
s addr=0x7ffc12580650
st=haha
st addr=0x7ffc12580690
亲,您是否对c++中的匿名对象、值传递、指针传递多了一点了解呢。:)
总结起来,java中传递参数从使用者的角度来讲,看起来是”引用传递的语义,部分非对象类型值传递语义”。但是从底层实现、c++的角度讲,java参数传递是”全部都是值传递语义”。
而且java相关的权威书籍中已经明确了,java中所有的参数传递都是值传递的语义。只是对象类型的值传递语义是指的指针,但看起来像是引用语义传递对象,这是很多人容易产生误解的一点。
简单的总结如下:
1、基本类型做的是值传递。
2、引用类型做的是地址传递。
当然,如果您觉得有谬误的地方,欢迎与我交流。谢谢批评指正哈。如果发现了,请纠正我。
I am open to change。:)