摘要:字節碼驗證于是就寫了以下的類,用來驗證然后,然后,看字節碼如下圖。以上,就是整個關于引用傳遞和值傳遞的理解,有說的不對的,望指正。
寫這個的原因主要是今天看到了知乎的一個問題,發現自己有些地方有點懵逼,寫下來記錄一下,知乎上排名第一的答案說的很清楚,不過看了以后依舊有點迷迷糊糊,所以自己寫了個幾行代碼測試。
首先上一個,感覺比較對的結論:
**Horstmann的《java核心技術》(中文第8版P115-P117)原文描述:
”java程序設計語言總是采用值調用。也就是說,方法得到的是所有參數值的一個拷貝,特別是,方法不能修改傳遞給它的任何參數變量的內容。“
”有些程序員(甚至是本書的作者),認為java程序設計語言對對象采用的是引用調用,實際上這種理解是不對的。”**
然后補充幾句我的理解:
首先,Java在傳遞過程中,傳遞的只有值,但是表現出來的形式,卻既有值傳遞也有引用傳遞,因此,沒必要糾結于名字,能理解原理即可。
在傳遞對象進函數時,對象的所有數據會被拷貝到局部變量中,這也就導致了局部變量修改其成員變量值時會導致原始的變量的成員變量值產生響應的改變,因為他們持有的成員變量的引用指向了同一個地址塊(內存空間)。
而對于傳遞8種基本變量時,也只是拷貝了值,因此對基本變量其本身的修改,無法導致原始變量的的修改。
不過這里需要考慮特殊情況,就是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對象。
?
同時,底部的Debug里拋出了個錯誤,說是無法獲取StringBuilder.toString(),也就進一步證明此處有新的String的產生。
?
到這里基本上就驗證了我的猜想,String +會產生一個新的String對象,既然這樣,反編譯下,看下字節碼,估計基本就搞定這個懵逼的問題了。
于是就寫了以下的類,用來驗證:
public class Main { public static void main(String[] args){ String s = "dove"; s += "233"; } }
然后javac,然后javap -c,看字節碼,如下圖。
??
嘗試著解釋下該部分代碼(不是很看的懂字節碼,所以有些解釋可能不是很規范,不過講道理大概意思不會差很遠)
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."":()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對象
從腦洞斷點以及最后的字節碼分析可以看出,s +="233",會導致一個新的String對象生成,也就是說,調用changeName2(String paramName)會使得paramName指向一個新的String對象,這樣就意味著,對該數據的改變并不會影響本身student.name的值,由此,String懵逼的問題也解決了。
以上,就是整個關于Java引用傳遞和值傳遞的理解,有說的不對的,望指正。
發現上次寫的時候忘了圖片,現在加上。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64995.html
摘要:值傳遞引用傳遞是值傳遞,是引用傳遞。但這影響會根據父類是屬于還是而有微妙差別。我們設想有一個父類,和兩個繼承了他的子類和。這時,子類修改該不會影響到父類本身,更不會傳遞到其他子類上。 Javascript有兩種基本數據類型,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...
摘要:引用可以被看作是文件系統中的硬鏈接。如果具有引用的數組被復制,其值不會解除引用。如果試圖這樣從函數返回引用,將會報錯,因為函數在試圖返回一個表達式的結果而不是一個引用的變量。這并不意味著變量內容被銷毀了。 1. 什么是引用 在 PHP 中引用是指用不同的名字訪問同一個變量內容。PHP 中的變量名和變量內容是不一樣的, 因此同樣的內容可以有不同的名字。最接近的比喻是 Unix 的文件名和...
摘要:所以傳遞給函數的值是這個值,所以函數執行結束原始變量并不會改變。傳值調用在傳值調用中,傳遞給函數參數是函數被調用時所傳實參的拷貝。引用類型變量的值是一個指針,指向堆內存中的實際對象。所以傳共享調用也可以說是傳值調用。 1. 例子 先來看兩個個來自于 《JavaScript 高級程序設計》P70-P71 的兩個例子。 1.1. 基本類型參數傳遞 function addTen(num) ...
摘要:原文鏈接我們推送到隊列的每個作業都存儲在按執行順序排序的某些存儲空間中,該存儲位置可以是數據庫,存儲或像這樣的第三方服務。這個數字從開始,在每次運行作業時不斷增加。 原文鏈接https://divinglaravel.com/queue-system/preparing-jobs-for-queue Every job we push to queue is stored in som...
閱讀 3609·2021-11-15 11:37
閱讀 2974·2021-11-12 10:36
閱讀 4403·2021-09-22 15:51
閱讀 2381·2021-08-27 16:18
閱讀 882·2019-08-30 15:44
閱讀 2164·2019-08-30 10:58
閱讀 1769·2019-08-29 17:18
閱讀 3269·2019-08-28 18:25