引用傳遞和值傳遞(pass by value vs pass by reference)

寫這個的原因主要是今天看到了知乎的一個問題江咳,發(fā)現(xiàn)自己有些地方有點懵逼,寫下來記錄一下哥放,知乎上排名第一的答案說的很清楚歼指,不過看了以后依舊有點迷迷糊糊爹土,所以自己寫了個幾行代碼測試。
首先上一個踩身,感覺比較對的結(jié)論:
Horstmann的《java核心技術(shù)》(中文第8版P115-P117)原文描述:
”java程序設(shè)計語言總是采用值調(diào)用胀茵。也就是說,方法得到的是所有參數(shù)值的一個拷貝挟阻,特別是琼娘,方法不能修改傳遞給它的任何參數(shù)變量的內(nèi)容「礁耄“
”有些程序員(甚至是本書的作者)脱拼,認(rèn)為java程序設(shè)計語言對對象采用的是引用調(diào)用,實際上這種理解是不對的坷备∠ㄅǎ”

然后補(bǔ)充幾句我的理解:

  1. 首先,Java在傳遞過程中省撑,傳遞的只有值赌蔑,但是表現(xiàn)出來的形式,卻既有值傳遞也有引用傳遞丁侄,因此惯雳,沒必要糾結(jié)于名字朝巫,能理解原理即可鸿摇。
  2. 在傳遞對象進(jìn)函數(shù)時,對象的所有數(shù)據(jù)會被拷貝到局部變量中劈猿,這也就導(dǎo)致了局部變量修改其成員變量值時會導(dǎo)致原始的變量的成員變量值產(chǎn)生響應(yīng)的改變拙吉,因為他們持有的成員變量的引用指向了同一個地址塊(內(nèi)存空間)。
  3. 而對于傳遞8種基本變量時揪荣,也只是拷貝了值筷黔,因此對基本變量其本身的修改,無法導(dǎo)致原始變量的的修改仗颈。
  4. 不過這里需要考慮特殊情況佛舱,就是String,其表現(xiàn)形式和8種基本變量一樣挨决,具體下文有分析请祖,而對于String為何要這么做,我也不清楚脖祈,不是很懂 jvm 和 Java 的設(shè)計肆捕。


一. 值類型和引用類型(此處先不考慮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;
        }
    }

對以上代碼進(jìn)行解釋

  • 首先addAge(student)調(diào)用的是addAge(Student paramStudent),該部分其實很好理解盖高,首先慎陵,paramStudet對象眼虱,拷貝了傳入的studet對象所有的數(shù)據(jù),因此paramStudet它所指向的地址席纽,其實和student是一樣的捏悬,所以,當(dāng)paramStudent改變它的age值時胆筒,其觸發(fā)的操作和student改變age的值是一樣的 ,因為他們都指向了同一個地址塊邮破。
  • 其次addAge(student.age)調(diào)用的是addAge(int paramAge),也很好理解仆救,paramAge只是拷貝了studet.age的值抒和,此處為10,然后改變了paramAge的值彤蔽,但此時paramAge與引用類型不同摧莽,它保存的只有一個值,所以其實這個parmaAge作為一個局部變量顿痪,并不能對原本的student.age產(chǎn)生任何影響

二. String的問題:

1. String問題來源

上面的例子其實很好搞清楚镊辕,但是我在碰到String的時候就有點懵逼了,如果調(diào)用以下方法,結(jié)果會如注釋顯示蚁袭。

    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)此處講道理被調(diào)用后應(yīng)該是"dove_2233",因為String是一個引用類型,也就是說此處的parmaName應(yīng)該是指向和傳入的參數(shù)指向了相同的一個地址塊征懈,然后對指向的內(nèi)存進(jìn)行了修改,然而結(jié)果并不是,原因就在于String是一個不可變的類型(為啥不可變呢,具體可以看String類的實現(xiàn),它是一個final class,并且其內(nèi)部正真保存著字符串的value[]也是不可變的(final),所以意味著修改Sting是不可能的)揩悄。

2.腦洞猜想可能情況

所以猜測上述的changeName2過程類似于

        FuckString fuckString = new FuckString();//paramName
        FuckString fuckString2 = new FuckString(fuckString);//構(gòu)造出的新的值
        fuckString = fuckString2;//把paramName指向構(gòu)造出的新值

然后卖哎,這就有點想不通了,不可變的類型删性,String 的 + 是怎么弄的呢亏娜?打個斷點試試看留攒,強(qiáng)制進(jìn)入纺腊,發(fā)現(xiàn)跳轉(zhuǎn)到了StringBuilder的構(gòu)造方法里凄敢,這說明應(yīng)該是構(gòu)造了一個新的StringBuilder對象喇勋。


19-58-14.jpg

同時兵怯,底部的Debug里拋出了個錯誤堕绩,說是無法獲取StringBuilder.toString(),也就進(jìn)一步證明此處有新的String的產(chǎn)生擒悬。

19-58-59.jpg

到這里基本上就驗證了我的猜想窍帝,String +會產(chǎn)生一個新的String對象榕茧,既然這樣垃沦,反編譯下,看下字節(jié)碼雪猪,估計基本就搞定這個懵逼的問題了栏尚。

3.字節(jié)碼驗證

于是就寫了以下的類,用來驗證:

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

然后javac,然后javap -c译仗,看字節(jié)碼,如下圖抬虽。

20-03-38.png

嘗試著解釋下該部分代碼(不是很看的懂字節(jié)碼,所以有些解釋可能不是很規(guī)范纵菌,不過講道理大概意思不會差很遠(yuǎn))

  • String s = "dove";部分字節(jié)碼及解釋
       0: ldc           #2                  // String dove
       2: astore_1

第0行阐污,將一個常量加載到操作數(shù)棧,也就是把“dove”這玩意,放進(jìn)了操作數(shù)棧(也不知道是什么東西咱圆,蛤蛤)
第2行笛辟,將一號數(shù)值(下劃線1代表一號,大概理解序苏,不是很準(zhǔn)確)從操作數(shù)棧存儲到局部變量表手幢,說白了就是把“dove”給存了起來?

  • s += "233";部分字節(jié)碼及解釋
       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虛擬機(jī)提供了一些用于直接操作操作數(shù)棧航唆,不是很懂胀蛮,貌似對整體理解影響不大,先過糯钙。
第7行粪狼,invokespecial 調(diào)用一些需要特殊處理的實例方法,包括實例初始化方法超营、私有方法和父類方法鸳玩,此處應(yīng)該是在初始化StringBuilder對象阅虫。
第10行演闭,將1號局部變量(下劃線1指代一號變量)加載到操作棧,這里應(yīng)該是指“dove”
第11行颓帝,調(diào)用對象的實例方法,此處就是調(diào)用StringBuilder.append,也就是把“dove”加到了StringBuilder
第14行米碰,將一個常量加載到操作數(shù)棧,就是把“233”載入
第16行购城,調(diào)用對象的實例方法,此處就是調(diào)用StringBuilder.append吕座,把“233”給加到“StringBuilder”中
第19行,調(diào)用對象的實例方法,此處就是調(diào)用StringBuilder.toString瘪板,而該方法吴趴,會觸發(fā)new String()的操作,因此侮攀,會返還一個新的String對象

4.最終結(jié)論:

從腦洞斷點以及最后的字節(jié)碼分析可以看出锣枝,s +="233",會導(dǎo)致一個新的String對象生成厢拭,也就是說,調(diào)用changeName2(String paramName)會使得paramName指向一個新的String對象撇叁,這樣就意味著供鸠,對該數(shù)據(jù)的改變并不會影響本身student.name的值,由此,String懵逼的問題也解決了陨闹。



以上楞捂,就是整個關(guān)于Java引用傳遞和值傳遞的理解,有說的不對的趋厉,望指正寨闹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市君账,隨后出現(xiàn)的幾起案子鼻忠,更是在濱河造成了極大的恐慌,老刑警劉巖杈绸,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帖蔓,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞳脓,警方通過查閱死者的電腦和手機(jī)塑娇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫侧,“玉大人埋酬,你說我怎么就攤上這事∩斩埃” “怎么了写妥?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵,是天一觀的道長审姓。 經(jīng)常有香客問我珍特,道長,這世上最難降的妖魔是什么魔吐? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任扎筒,我火速辦了婚禮,結(jié)果婚禮上酬姆,老公的妹妹穿的比我還像新娘嗜桌。我一直安慰自己,他們只是感情好辞色,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布骨宠。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪层亿。 梳的紋絲不亂的頭發(fā)上壶唤,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天,我揣著相機(jī)與錄音棕所,去河邊找鬼闸盔。 笑死,一個胖子當(dāng)著我的面吹牛琳省,可吹牛的內(nèi)容都是我干的迎吵。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼针贬,長吁一口氣:“原來是場噩夢啊……” “哼击费!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起桦他,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤蔫巩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后快压,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圆仔,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年蔫劣,在試婚紗的時候發(fā)現(xiàn)自己被綠了坪郭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡脉幢,死狀恐怖歪沃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嫌松,我是刑警寧澤沪曙,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站萎羔,受9級特大地震影響液走,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜外驱,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一育灸、第九天 我趴在偏房一處隱蔽的房頂上張望腻窒。 院中可真熱鬧昵宇,春花似錦、人聲如沸儿子。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒋譬,卻和暖如春割岛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背犯助。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工癣漆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剂买。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓惠爽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞬哼。 傳聞我的和親對象是個殘疾皇子婚肆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容