java引用传递值传递的"深入"解析与c++中的值传递

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。:)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值