• Java对象引用传递探索


    一直认为自己对对象传递理解的颇为深刻,没想到最近一次的编码中,就犯下了这样的错误,令自己排查了很久才找到问题的根源, 辅以小case记录以自省。

    代码如下:

    public class ObjReference {
        String name = "ObjectReference";
        String id = UUID.randomUUID().toString();
        
        public ObjReference(){}
        
        public ObjReference(String name, String id){
            this.name = name;
            this.id = id;
        }
        
        public String toSelfAttr(){
            return "name = " + name + ", id = " + id;
        }
        
        public void fillSelf(ObjReference obj){
            /*System.out.println("old address: " + obj);*/
            obj = cloneSelf();
            /*System.out.println("after clone,it's address: " + obj);*/
        }
        
        public ObjReference cloneSelf(){
            ObjReference obj = new ObjReference(
                    "cloneSelf",UUID.randomUUID().toString());
            /*System.out.println("clone ObjReference's address: " + obj.toString()
                    + ", and its selfAttr: " + obj.toSelfAttr());*/
            return obj;        
        }
        
        public static void main(String[]  args){
            ObjReference obj = new ObjReference();
            System.out.println("old ObjReference's address: " + obj.toString()
                    + ", and its selfAttr: " + obj.toSelfAttr());
            obj.fillSelf(obj);
            System.out.println("after filled, ObjReference's address: " + obj.toString()
                    + ", and its selfAttr: " + obj.toSelfAttr());
        }
        
    }

    各位看官,运行结果会是如何? fillSelf()之后,对象本身属性改变是否会生效?  来看运行结果:

    old ObjReference's address: com.chq.study.ObjReference@bb494b, and its selfAttr: name = ObjectReference, id = 91f17723-9227-461e-878e-51f7a3eedb0f
    after filled, ObjReference's address: com.chq.study.ObjReference@bb494b, and its selfAttr: name = ObjectReference, id = 91f17723-9227-461e-878e-51f7a3eedb0f

    我们会发现,对象地址没有改变(这个好理解,对象是按引用传递的),但出乎我预料的,对象属性也没有任何变化.... why?

    放开fillSelf() & cloneSelf()的注释, 再次运行下,看看之间发生了什么。

    old ObjReference's address: com.chq.study.ObjReference@1636e4e, and its selfAttr: name = ObjectReference, id = c10f9c98-8f15-4343-85db-7a85e21b22d7
    old address: com.chq.study.ObjReference@1636e4e
    clone ObjReference's address: com.chq.study.ObjReference@df0438, and its selfAttr: name = cloneSelf, id = eb117f7a-3463-4371-b723-4f43a041018d
    after clone,it's address: com.chq.study.ObjReference@df0438
    after filled, ObjReference's address: com.chq.study.ObjReference@1636e4e, and its selfAttr: name = ObjectReference, id = c10f9c98-8f15-4343-85db-7a85e21b22d7

    橘黄色背景的,说明了最终结果没有变化。 青色背景的,说明对象在fill过程中,实际是有变化的,不仅是对象属性,其address也是发生了变化的。

    既然address都已经变化了,那为何最终结果并没有体现出这种变化呢?这个说明了什么? 

    大家都知道的:对象传参时,是按引用传的,这个引用,指的是指向内存堆heap中实际对象的地址,所有对此对象的改变,实际是发生在heap中的那个实际对象体块上。

    可这个解释不了示例中的现象,因为对象地址也是改变了的,虽然new了新对象,但我们确实将新对象的address返回并覆盖原对象地址了,那为何没有得到预期的结果?

    大家未必知道的:对象引用传递时,对象引用本身是按值(by-value)传递的,是保存在thread stack上的,即copy了一份出来进行传递的,如同基本类型的传递。

    所以虽然我们明确改变了对象引用指向的heap地址,以及传递对象本身的地址(是对象本身地址的copy,如同指针),但实际对象本身地址并未改变,所以最终结果不会有变化。

    这同时也是我所犯下的错误。所以如果想使用类似此种实现,有两种办法:

    1、原对象不要先指向任何对象(无论new还是null),仅声明并直接指向待构造新对象的方法即可(如: ObjReference obj2 = test.cloneSelf())  

    2、改变对象的方法中,还使用原来对象,不要new新的对象出来(确保对象引用本身没有变化,变化的仅是heap中的)

    我们可以在main中屏蔽掉之前的代码,增加如下代码,进行方式1的验证:

        public static void main(String[]  args){
            /*ObjReference obj = new ObjReference();
            System.out.println("old ObjReference's address: " + obj.toString()
                    + ", and its selfAttr: " + obj.toSelfAttr());
            obj.fillSelf(obj);
            System.out.println("after filled, ObjReference's address: " + obj.toString()
                    + ", and its selfAttr: " + obj.toSelfAttr());*/
            ObjReference test = new ObjReference();
            ObjReference obj1 = null;
            test.fillSelf(obj1);
            System.out.println(null == obj1 ? "obj1 is null." : 
                    "obj1 is : " + obj1.toSelfAttr());
            ObjReference obj2 = test.cloneSelf();
            System.out.println("obj2 is : " + obj2.toSelfAttr());
        }

    运行结果:

    old address: null
    clone ObjReference's address: com.chq.study.ObjReference@18e261d, and its selfAttr: name = cloneSelf, id = 37be891f-127c-4b70-a992-fa842d79ca2e
    after clone,it's address: com.chq.study.ObjReference@18e261d
    obj1 is null.
    clone ObjReference's address: com.chq.study.ObjReference@1684706, and its selfAttr: name = cloneSelf, id = efc60431-d20a-4614-83ff-d3eaa018c41c
    obj2 is : name = cloneSelf, id = efc60431-d20a-4614-83ff-d3eaa018c41c

     我的一个疑问,盼高人指点: java中有可以查看对象引用本身地址(引用本身的指针)的方法或者工具么? 如有,可对此做更加强有力的支撑验证。

  • 相关阅读:
    Android新手引导库推荐
    windbg 常调用指令
    通过Hook NtOpenProcess 函数实现反调试
    PE文件
    消息机制
    软件调试
    异常(2) --- 编译器对于SEH异常的拓展
    异常(1)
    等待对象
    进程与线程
  • 原文地址:https://www.cnblogs.com/FallingAutumn/p/3177992.html
Copyright © 2020-2023  润新知