Java 到底是值传递还是引用传递
作者:Intopass
鏈接:https://www.zhihu.com/question/31203609/answer/50992895
來(lái)源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
首先,不要糾結(jié)于 Pass By Value 和 Pass By Reference 的字面上的意義,否則很容易陷入所謂的“一切傳引用其實(shí)本質(zhì)上是傳值”這種并不能解決問(wèn)題無(wú)意義論戰(zhàn)中。
更何況,要想知道Java到底是傳值還是傳引用,起碼你要先知道傳值和傳引用的準(zhǔn)確含義吧?可是如果你已經(jīng)知道了這兩個(gè)名字的準(zhǔn)確含義,那么你自己就能判斷Java到底是傳值還是傳引用。
這就好像用大學(xué)的名詞來(lái)解釋高中的題目,對(duì)于初學(xué)者根本沒(méi)有任何意義。
一、搞清楚 基本類型 和 引用類型的不同之處
int num = 10; String str = "hello"; <img data-rawheight="458" src="https://pic3.zhimg.com/50/166032bc90958c21604110441ad03f45_hd.jpg" data-size="normal" data-rawwidth="728" class="origin_image zh-lightbox-thumb" width="728" data-original="https://pic3.zhimg.com/166032bc90958c21604110441ad03f45_r.jpg">如圖所示,num是基本類型,值就直接保存在變量中。而str是引用類型,變量中保存的只是實(shí)際對(duì)象的地址。一般稱這種變量為"引用",引用指向?qū)嶋H對(duì)象,實(shí)際對(duì)象中保存著內(nèi)容。
二、搞清楚賦值運(yùn)算符(=)的作用
num = 20; str = "java"; <img data-rawheight="572" src="https://pic4.zhimg.com/50/287c0efbb179638cf4cf27cbfdf3e746_hd.jpg" data-size="normal" data-rawwidth="714" class="origin_image zh-lightbox-thumb" width="714" data-original="https://pic4.zhimg.com/287c0efbb179638cf4cf27cbfdf3e746_r.jpg">對(duì)于基本類型 num ,賦值運(yùn)算符會(huì)直接改變變量的值,原來(lái)的值被覆蓋掉。
對(duì)于引用類型 str,賦值運(yùn)算符會(huì)改變引用中所保存的地址,原來(lái)的地址被覆蓋掉。但是原來(lái)的對(duì)象不會(huì)被改變(重要)。
如上圖所示,"hello" 字符串對(duì)象沒(méi)有被改變。(沒(méi)有被任何引用所指向的對(duì)象是垃圾,會(huì)被垃圾回收器回收)
三、調(diào)用方法時(shí)發(fā)生了什么?參數(shù)傳遞基本上就是賦值操作。
第一個(gè)例子:基本類型void foo(int value) {value = 100; } foo(num); // num 沒(méi)有被改變 第二個(gè)例子:沒(méi)有提供改變自身方法的引用類型 void foo(String text) {text = "windows"; } foo(str); // str 也沒(méi)有被改變 第三個(gè)例子:提供了改變自身方法的引用類型 StringBuilder sb = new StringBuilder("iphone"); void foo(StringBuilder builder) {builder.append("4"); } foo(sb); // sb 被改變了,變成了"iphone4"。 第四個(gè)例子:提供了改變自身方法的引用類型,但是不使用,而是使用賦值運(yùn)算符。 StringBuilder sb = new StringBuilder("iphone"); void foo(StringBuilder builder) {builder = new StringBuilder("ipad"); } foo(sb); // sb 沒(méi)有被改變,還是 "iphone"。
重點(diǎn)理解為什么,第三個(gè)例子和第四個(gè)例子結(jié)果不同?
下面是第三個(gè)例子的圖解:
<img data-rawheight="398" src="https://pic2.zhimg.com/50/d8b82e07ea21375ca6b300f9162aa95f_hd.jpg" data-size="normal" data-rawwidth="772" class="origin_image zh-lightbox-thumb" width="772" data-original="https://pic2.zhimg.com/d8b82e07ea21375ca6b300f9162aa95f_r.jpg">builder.append("4")之后
<img data-rawheight="424" src="https://pic1.zhimg.com/50/ff2ede9c6c55568d42425561f25a0fd7_hd.jpg" data-size="normal" data-rawwidth="696" class="origin_image zh-lightbox-thumb" width="696" data-original="https://pic1.zhimg.com/ff2ede9c6c55568d42425561f25a0fd7_r.jpg">下面是第四個(gè)例子的圖解:
<img data-rawheight="398" src="https://pic2.zhimg.com/50/d8b82e07ea21375ca6b300f9162aa95f_hd.jpg" data-size="normal" data-rawwidth="772" class="origin_image zh-lightbox-thumb" width="772" data-original="https://pic2.zhimg.com/d8b82e07ea21375ca6b300f9162aa95f_r.jpg">?
builder = new StringBuilder("ipad"); 之后
<img data-rawheight="438" src="https://pic1.zhimg.com/50/46fa5f10cc135a3ca087dae35a5211bd_hd.jpg" data-size="normal" data-rawwidth="710" class="origin_image zh-lightbox-thumb" width="710" data-original="https://pic1.zhimg.com/46fa5f10cc135a3ca087dae35a5211bd_r.jpg">2018年1月31日添加部分內(nèi)容:
這個(gè)答案點(diǎn)贊的不少,雖然當(dāng)時(shí)回答時(shí)并沒(méi)有講的特別詳細(xì),今天就稍微多講一些各種類型數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式。
從局部變量/方法參數(shù)開(kāi)始講起:
局部變量和方法參數(shù)在jvm中的儲(chǔ)存方法是相同的,都是在棧上開(kāi)辟空間來(lái)儲(chǔ)存的,隨著進(jìn)入方法開(kāi)辟,退出方法回收。以32位JVM為例,boolean/byte/short/char/int/float以及引用都是分配4字節(jié)空間,long/double分配8字節(jié)空間。對(duì)于每個(gè)方法來(lái)說(shuō),最多占用多少空間是一定的,這在編譯時(shí)就可以計(jì)算好。
我們都知道JVM內(nèi)存模型中有,stack和heap的存在,但是更準(zhǔn)確的說(shuō),是每個(gè)線程都分配一個(gè)獨(dú)享的stack,所有線程共享一個(gè)heap。對(duì)于每個(gè)方法的局部變量來(lái)說(shuō),是絕對(duì)無(wú)法被其他方法,甚至其他線程的同一方法所訪問(wèn)到的,更遑論修改。
當(dāng)我們?cè)诜椒ㄖ新暶饕粋€(gè) int i = 0,或者 Object obj = null 時(shí),僅僅涉及stack,不影響到heap,當(dāng)我們 new Object() 時(shí),會(huì)在heap中開(kāi)辟一段內(nèi)存并初始化Object對(duì)象。當(dāng)我們將這個(gè)對(duì)象賦予obj變量時(shí),僅僅是stack中代表obj的那4個(gè)字節(jié)變更為這個(gè)對(duì)象的地址。
數(shù)組類型引用和對(duì)象:
當(dāng)我們聲明一個(gè)數(shù)組時(shí),如int[] arr = new int[10],因?yàn)閿?shù)組也是對(duì)象,arr實(shí)際上是引用,stack上僅僅占用4字節(jié)空間,new int[10]會(huì)在heap中開(kāi)辟一個(gè)數(shù)組對(duì)象,然后arr指向它。
當(dāng)我們聲明一個(gè)二維數(shù)組時(shí),如 int[][] arr2 = new int[2][4],arr2同樣僅在stack中占用4個(gè)字節(jié),會(huì)在內(nèi)存中開(kāi)辟一個(gè)長(zhǎng)度為2的,類型為int[]的數(shù)組,然后arr2指向這個(gè)數(shù)組。這個(gè)數(shù)組內(nèi)部有兩個(gè)引用(大小為4字節(jié)),分別指向兩個(gè)長(zhǎng)度為4的類型為int的數(shù)組。
<img data-rawheight="740" src="https://pic4.zhimg.com/50/v2-6590cb935ae8bf3b7241cb309fe041d7_hd.jpg" data-size="normal" data-rawwidth="1498" class="origin_image zh-lightbox-thumb" width="1498" data-original="https://pic4.zhimg.com/v2-6590cb935ae8bf3b7241cb309fe041d7_r.jpg">?
所以當(dāng)我們傳遞一個(gè)數(shù)組引用給一個(gè)方法時(shí),數(shù)組的元素是可以被改變的,但是無(wú)法讓數(shù)組引用指向新的數(shù)組。
你還可以這樣聲明:int[][] arr3 = new int[3][],這時(shí)內(nèi)存情況如下圖
<img data-rawheight="656" src="https://pic1.zhimg.com/50/v2-fdc86227021d56a02b559d6485983c71_hd.jpg" data-size="normal" data-rawwidth="1408" class="origin_image zh-lightbox-thumb" width="1408" data-original="https://pic1.zhimg.com/v2-fdc86227021d56a02b559d6485983c71_r.jpg">?
你還可以這樣 arr3[0] = new int [5]; arr3[1] = arr2[0];
<img data-rawheight="1026" src="https://pic3.zhimg.com/50/v2-fdc5e737a95d625a47d66ab61e4a2f55_hd.jpg" data-size="normal" data-rawwidth="1758" class="origin_image zh-lightbox-thumb" width="1758" data-original="https://pic3.zhimg.com/v2-fdc5e737a95d625a47d66ab61e4a2f55_r.jpg">?
關(guān)于String:
原本回答中關(guān)于String的圖解是簡(jiǎn)化過(guò)的,實(shí)際上String對(duì)象內(nèi)部?jī)H需要維護(hù)三個(gè)變量,char[] chars, int startIndex, int length。而chars在某些情況下是可以共用的。但是因?yàn)镾tring被設(shè)計(jì)成為了不可變類型,所以你思考時(shí)把String對(duì)象簡(jiǎn)化考慮也是可以的。
String str = new String("hello")
<img data-rawheight="628" src="https://pic1.zhimg.com/50/v2-a143d0a3594d06f54c6853c46c429e08_hd.jpg" data-size="normal" data-rawwidth="1394" class="origin_image zh-lightbox-thumb" width="1394" data-original="https://pic1.zhimg.com/v2-a143d0a3594d06f54c6853c46c429e08_r.jpg">?
當(dāng)然某些JVM實(shí)現(xiàn)會(huì)把"hello"字面量生成的String對(duì)象放到常量池中,而常量池中的對(duì)象可以實(shí)際分配在heap中,有些實(shí)現(xiàn)也許會(huì)分配在方法區(qū),當(dāng)然這對(duì)我們理解影響不大。
轉(zhuǎn)載于:https://www.cnblogs.com/guoyaohua/p/8436855.html
總結(jié)
以上是生活随笔為你收集整理的Java 到底是值传递还是引用传递的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 5G全球声量升级:Verizon宣布固定
- 下一篇: Java EE(五)