引用传递和值传递(pass by value vs pass by reference)

写这个的原因主要是今天看到了知乎的一个问题,发现自己有些地方有点懵逼,写下来记录一下,知乎上排名第一的答案说的很清楚,不过看了以后依旧有点迷迷糊糊,所以自己写了个几行代码测试。
首先上一个,感觉比较对的结论:
Horstmann的《java核心技术》(中文第8版P115-P117)原文描述:
”java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。“
”有些程序员(甚至是本书的作者),认为java程序设计语言对对象采用的是引用调用,实际上这种理解是不对的。”

然后补充几句我的理解:

  1. 首先,Java在传递过程中,传递的只有值,但是表现出来的形式,却既有值传递也有引用传递,因此,没必要纠结于名字,能理解原理即可。
  2. 在传递对象进函数时,对象的所有数据会被拷贝到局部变量中,这也就导致了局部变量修改其成员变量值时会导致原始的变量的成员变量值产生响应的改变,因为他们持有的成员变量的引用指向了同一个地址块(内存空间)。
  3. 而对于传递8种基本变量时,也只是拷贝了值,因此对基本变量其本身的修改,无法导致原始变量的的修改。
  4. 不过这里需要考虑特殊情况,就是String,其表现形式和8种基本变量一样,具体下文有分析,而对于String为何要这么做,我也不清楚,不是很懂 jvm 和 Java 的设计。


一. 值类型和引用类型(此处先不考虑String)的传递:

public class Student {
    int age;
    String name;
}
    public class TestReference {
        public static void main(String[] args){
            Student student = new Student();
            student.age = 10;
            System.out.println(student.age);//10
            addAge(student);
            System.out.println(student.age);//11
            addAge(student.age);
            System.out.println(student.age);//11
        }
        
        static public void addAge(Student paramStudent){
            paramStudent.age = 11;
        }
       
        static public void addAge(int paramAge){
            paramAge = 12;
        }
    }

对以上代码进行解释

  • 首先addAge(student)调用的是addAge(Student paramStudent),该部分其实很好理解,首先,paramStudet对象,拷贝了传入的studet对象所有的数据,因此paramStudet它所指向的地址,其实和student是一样的,所以,当paramStudent改变它的age值时,其触发的操作和student改变age的值是一样的 ,因为他们都指向了同一个地址块。
  • 其次addAge(student.age)调用的是addAge(int paramAge),也很好理解,paramAge只是拷贝了studet.age的值,此处为10,然后改变了paramAge的值,但此时paramAge与引用类型不同,它保存的只有一个值,所以其实这个parmaAge作为一个局部变量,并不能对原本的student.age产生任何影响

二. String的问题:

1. String问题来源

上面的例子其实很好搞清楚,但是我在碰到String的时候就有点懵逼了,如果调用以下方法,结果会如注释显示。

    public static void main(String[] args){
        Student student = new Student();
        student.age = 10;
        student.name = "dove";
        changeName(student);
        System.out.println(student.name);//dove_2
        changeName(student.name);
        System.out.println(student.name);//dove_2
        changeName2(student.name); 
        System.out.println(student.name);//dove_2         
    }      
    static void changeName(Student paramStudent){
        paramStudent.name = "dove_2";
    }
    static void changeName(String paramName){
        paramName = "dove_3";
    }
    static void changeName2(String paramName){
        paramName += "233";
    }

changeName2(String paramName)此处讲道理被调用后应该是"dove_2233",因为String是一个引用类型,也就是说此处的parmaName应该是指向和传入的参数指向了相同的一个地址块,然后对指向的内存进行了修改,然而结果并不是,原因就在于String是一个不可变的类型(为啥不可变呢,具体可以看String类的实现,它是一个final class,并且其内部正真保存着字符串的value[]也是不可变的(final),所以意味着修改Sting是不可能的)。

2.脑洞猜想可能情况

所以猜测上述的changeName2过程类似于

        FuckString fuckString = new FuckString();//paramName
        FuckString fuckString2 = new FuckString(fuckString);//构造出的新的值
        fuckString = fuckString2;//把paramName指向构造出的新值

然后,这就有点想不通了,不可变的类型,String 的 + 是怎么弄的呢?打个断点试试看,强制进入,发现跳转到了StringBuilder的构造方法里,这说明应该是构造了一个新的StringBuilder对象。


19-58-14.jpg

同时,底部的Debug里抛出了个错误,说是无法获取StringBuilder.toString(),也就进一步证明此处有新的String的产生。

19-58-59.jpg

到这里基本上就验证了我的猜想,String +会产生一个新的String对象,既然这样,反编译下,看下字节码,估计基本就搞定这个懵逼的问题了。

3.字节码验证

于是就写了以下的类,用来验证:

    public class Main {
        public static void main(String[] args){
            String s = "dove";
            s += "233";
        }
    }

然后javac,然后javap -c,看字节码,如下图。

20-03-38.png

尝试着解释下该部分代码(不是很看的懂字节码,所以有些解释可能不是很规范,不过讲道理大概意思不会差很远)

  • String s = "dove";部分字节码及解释
       0: ldc           #2                  // String dove
       2: astore_1

第0行,将一个常量加载到操作数栈,也就是把“dove”这玩意,放进了操作数栈(也不知道是什么东西,蛤蛤)
第2行,将一号数值(下划线1代表一号,大概理解,不是很准确)从操作数栈存储到局部变量表,说白了就是把“dove”给存了起来?

  • s += "233";部分字节码及解释
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1    
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String 233
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

第3行,这个很明显,不google也知道,new StringBuilder(),也就是搞了个StringBUilder的实例。
第6行,Java虚拟机提供了一些用于直接操作操作数栈,不是很懂,貌似对整体理解影响不大,先过。
第7行,invokespecial 调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法,此处应该是在初始化StringBuilder对象。
第10行,将1号局部变量(下划线1指代一号变量)加载到操作栈,这里应该是指“dove”
第11行,调用对象的实例方法,此处就是调用StringBuilder.append,也就是把“dove”加到了StringBuilder
第14行,将一个常量加载到操作数栈,就是把“233”载入
第16行,调用对象的实例方法,此处就是调用StringBuilder.append,把“233”给加到“StringBuilder”中
第19行,调用对象的实例方法,此处就是调用StringBuilder.toString,而该方法,会触发new String()的操作,因此,会返还一个新的String对象

4.最终结论:

从脑洞断点以及最后的字节码分析可以看出,s +="233",会导致一个新的String对象生成,也就是说,调用changeName2(String paramName)会使得paramName指向一个新的String对象,这样就意味着,对该数据的改变并不会影响本身student.name的值,由此,String懵逼的问题也解决了。



以上,就是整个关于Java引用传递和值传递的理解,有说的不对的,望指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容