摘要:字節(jié)碼驗(yàn)證于是就寫了以下的類,用來驗(yàn)證然后,然后,看字節(jié)碼如下圖。以上,就是整個關(guān)于引用傳遞和值傳遞的理解,有說的不對的,望指正。
寫這個的原因主要是今天看到了知乎的一個問題,發(fā)現(xiàn)自己有些地方有點(diǎn)懵逼,寫下來記錄一下,知乎上排名第一的答案說的很清楚,不過看了以后依舊有點(diǎn)迷迷糊糊,所以自己寫了個幾行代碼測試。
首先上一個,感覺比較對的結(jié)論:
**Horstmann的《java核心技術(shù)》(中文第8版P115-P117)原文描述:
”java程序設(shè)計語言總是采用值調(diào)用。也就是說,方法得到的是所有參數(shù)值的一個拷貝,特別是,方法不能修改傳遞給它的任何參數(shù)變量的內(nèi)容?!?br>”有些程序員(甚至是本書的作者),認(rèn)為java程序設(shè)計語言對對象采用的是引用調(diào)用,實(shí)際上這種理解是不對的?!?*
然后補(bǔ)充幾句我的理解:
首先,Java在傳遞過程中,傳遞的只有值,但是表現(xiàn)出來的形式,卻既有值傳遞也有引用傳遞,因此,沒必要糾結(jié)于名字,能理解原理即可。
在傳遞對象進(jìn)函數(shù)時,對象的所有數(shù)據(jù)會被拷貝到局部變量中,這也就導(dǎo)致了局部變量修改其成員變量值時會導(dǎo)致原始的變量的成員變量值產(chǎn)生響應(yīng)的改變,因?yàn)樗麄兂钟械某蓡T變量的引用指向了同一個地址塊(內(nèi)存空間)。
而對于傳遞8種基本變量時,也只是拷貝了值,因此對基本變量其本身的修改,無法導(dǎo)致原始變量的的修改。
不過這里需要考慮特殊情況,就是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),該部分其實(shí)很好理解,首先,paramStudet對象,拷貝了傳入的studet對象所有的數(shù)據(jù),因此paramStudet它所指向的地址,其實(shí)和student是一樣的,所以,當(dāng)paramStudent改變它的age值時,其觸發(fā)的操作和student改變age的值是一樣的 ,因?yàn)樗麄兌贾赶蛄送粋€地址塊。
其次addAge(student.age)調(diào)用的是addAge(int paramAge),也很好理解,paramAge也只是拷貝了studet.age的值,此處為10,然后改變了paramAge的值,但此時paramAge與引用類型不同,它保存的只有一個值,所以其實(shí)這個parmaAge作為一個局部變量,并不能對原本的student.age產(chǎn)生任何影響
二. String的問題: 1. String問題來源上面的例子其實(shí)很好搞清楚,但是我在碰到String的時候就有點(diǎn)懵逼了,如果調(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",因?yàn)?b>String是一個引用類型,也就是說此處的parmaName應(yīng)該是指向和傳入的參數(shù)指向了相同的一個地址塊,然后對指向的內(nèi)存進(jìn)行了修改,然而結(jié)果并不是,原因就在于String是一個不可變的類型(為啥不可變呢,具體可以看String類的實(shí)現(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)造出的新值
然后,這就有點(diǎn)想不通了,不可變的類型,String 的 + 是怎么弄的呢?打個斷點(diǎn)試試看,強(qiáng)制進(jìn)入,發(fā)現(xiàn)跳轉(zhuǎn)到了StringBuilder的構(gòu)造方法里,這說明應(yīng)該是構(gòu)造了一個新的StringBuilder對象。
?
同時,底部的Debug里拋出了個錯誤,說是無法獲取StringBuilder.toString(),也就進(jìn)一步證明此處有新的String的產(chǎn)生。
?
到這里基本上就驗(yàn)證了我的猜想,String +會產(chǎn)生一個新的String對象,既然這樣,反編譯下,看下字節(jié)碼,估計基本就搞定這個懵逼的問題了。
于是就寫了以下的類,用來驗(yàn)證:
public class Main { public static void main(String[] args){ String s = "dove"; s += "233"; } }
然后javac,然后javap -c,看字節(jié)碼,如下圖。
??
嘗試著解釋下該部分代碼(不是很看的懂字節(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."":()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的實(shí)例。
第6行,Java虛擬機(jī)提供了一些用于直接操作操作數(shù)棧,不是很懂,貌似對整體理解影響不大,先過。
第7行,invokespecial 調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法、私有方法和父類方法,此處應(yīng)該是在初始化StringBuilder對象。
第10行,將1號局部變量(下劃線1指代一號變量)加載到操作棧,這里應(yīng)該是指“dove”
第11行,調(diào)用對象的實(shí)例方法,此處就是調(diào)用StringBuilder.append,也就是把“dove”加到了StringBuilder中
第14行,將一個常量加載到操作數(shù)棧,就是把“233”載入
第16行,調(diào)用對象的實(shí)例方法,此處就是調(diào)用StringBuilder.append,把“233”給加到“StringBuilder”中
第19行,調(diào)用對象的實(shí)例方法,此處就是調(diào)用StringBuilder.toString,而該方法,會觸發(fā)new String()的操作,因此,會返還一個新的String對象
從腦洞斷點(diǎn)以及最后的字節(jié)碼分析可以看出,s +="233",會導(dǎo)致一個新的String對象生成,也就是說,調(diào)用changeName2(String paramName)會使得paramName指向一個新的String對象,這樣就意味著,對該數(shù)據(jù)的改變并不會影響本身student.name的值,由此,String懵逼的問題也解決了。
以上,就是整個關(guān)于Java引用傳遞和值傳遞的理解,有說的不對的,望指正。
發(fā)現(xiàn)上次寫的時候忘了圖片,現(xiàn)在加上。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64995.html
摘要:值傳遞引用傳遞是值傳遞,是引用傳遞。但這影響會根據(jù)父類是屬于還是而有微妙差別。我們設(shè)想有一個父類,和兩個繼承了他的子類和。這時,子類修改該不會影響到父類本身,更不會傳遞到其他子類上。 Javascript有兩種基本數(shù)據(jù)類型,Primitive和Object。Object是properties的聚合,其property可以是Object也可以是Primitive。Primitive只有v...
Java is pass-by-value. Pass by value: make a copy in memory of the actual parameters value that is passed in. Pass by reference: pass a copy of the address of the actual parameter. This code will no...
摘要:引用可以被看作是文件系統(tǒng)中的硬鏈接。如果具有引用的數(shù)組被復(fù)制,其值不會解除引用。如果試圖這樣從函數(shù)返回引用,將會報錯,因?yàn)楹瘮?shù)在試圖返回一個表達(dá)式的結(jié)果而不是一個引用的變量。這并不意味著變量內(nèi)容被銷毀了。 1. 什么是引用 在 PHP 中引用是指用不同的名字訪問同一個變量內(nèi)容。PHP 中的變量名和變量內(nèi)容是不一樣的, 因此同樣的內(nèi)容可以有不同的名字。最接近的比喻是 Unix 的文件名和...
摘要:所以傳遞給函數(shù)的值是這個值,所以函數(shù)執(zhí)行結(jié)束原始變量并不會改變。傳值調(diào)用在傳值調(diào)用中,傳遞給函數(shù)參數(shù)是函數(shù)被調(diào)用時所傳實(shí)參的拷貝。引用類型變量的值是一個指針,指向堆內(nèi)存中的實(shí)際對象。所以傳共享調(diào)用也可以說是傳值調(diào)用。 1. 例子 先來看兩個個來自于 《JavaScript 高級程序設(shè)計》P70-P71 的兩個例子。 1.1. 基本類型參數(shù)傳遞 function addTen(num) ...
摘要:原文鏈接我們推送到隊(duì)列的每個作業(yè)都存儲在按執(zhí)行順序排序的某些存儲空間中,該存儲位置可以是數(shù)據(jù)庫,存儲或像這樣的第三方服務(wù)。這個數(shù)字從開始,在每次運(yùn)行作業(yè)時不斷增加。 原文鏈接https://divinglaravel.com/queue-system/preparing-jobs-for-queue Every job we push to queue is stored in som...
閱讀 3669·2021-11-15 11:37
閱讀 2993·2021-11-12 10:36
閱讀 4469·2021-09-22 15:51
閱讀 2395·2021-08-27 16:18
閱讀 902·2019-08-30 15:44
閱讀 2177·2019-08-30 10:58
閱讀 1794·2019-08-29 17:18
閱讀 3290·2019-08-28 18:25