java中對象做爲參數傳遞給一個方法,究竟是值傳遞,仍是引用傳遞?java
String和int參數傳遞是按值傳遞仍是引用傳遞?面試
一道面試題目,String的傳遞:編程
public String change(String s){ s = "222"; return s; } public static void main(Stirng[] args){ String s = "111"; change(s); sout(s); }
Java 編程語言只有值傳遞參數。當一個對象實例做爲一個參數被傳遞到方法中時,參數的值就是該對象的引用一個副本。指向同一個對象,對象的內容能夠在被調用的方法中改變,但對象的引用(不是引用的副本)是永遠不會改變的。 我看到題目愣了一下,原本不假思考的結果是111,但仔細想,String是對象類型的,對象傳遞的是地址,那麼地址傳遞到方法裏面後,將指向修改爲222,那麼結果應該是222纔對。實際偏偏相反。app
java傳遞參數都是值,若是是對象的話,就是將引用的值複製一份給方法當參數。若是是根據引用把堆裏的對象修改了,那麼對象真被修改了,不過不是被建立賦值給的那個引用修改的,是方法裏的一個複製的引用副本給修改的。換句話說,施瓦星格的媳被施瓦星格的克隆人親了下。jvm
用實例去理解,其實這個理解也就是根據jdk的結果告訴我本身記住規則是這樣的,之後要記住。編程語言
public String change(String s, int i, StringBuffer sb, Person p){ s="123"; i=3; sb.append("woshi"); p.setAge(100); sb = new StringBuffer("sbsb"); p = new Person("bb",44); return s; } @Test public void testChange(){ StringBuffer sb = new StringBuffer("buff"); String s = "aaa"; int i = 1; Person p = new Person("aa",12); i=2; change(s,i,sb,p); // s="222"; System.out.println(s); System.out.println(i); System.out.println(sb.toString()); System.out.println(p); }
這裏一共測試了String,int,一個對象StringBuffer,一個對象people。讓咱們來仔細看看這些傳遞都發生了什麼。我想有很大一部分人都猜不出打印結果。測試
|
咱們來一個個分析。
1 |
|
這裏,jvm建立一個變量引用s,在堆中建立一個對象aaa,將aaa放進常量池。s指向aaa。
而後就到了change方法裏。這裏這樣理解:將s引用的一個拷貝傳給方法change。這樣change有一個變量s,這個s也是指向aaa的。那麼咱們來經過debug來看後來發生了什麼。
1.s指向aaa的時候:
2.s運行到change方法裏的時候
而後看s再次賦值的時候:
而後咱們運行結束change方法後到主方法裏:
到這裏s就結束了。那麼若是咱們按照傳遞的是s這個變量的引用,即String s="aaa"中這個s自己,那麼,s這個自己是個變量,s指向aaa,在方法change裏s又指向了123,回到主方法後s變量的指向被改變了?錯!顯然s仍舊是aaa,那麼只能這樣理解:s傳遞到方法裏的時候,複製了s指向的地址給change,change裏的s是另外一個s,s指向aaa(@718),而後在change中s又指向了123(@731),因爲String是不可變類(final and Immutable),這裏只是把副本s的指向修改爲731,原地址718裏的對象沒有發生改變由於String不可變。那麼,回到主方法的時候,s變量自己沒有任何改變,s仍舊指向地址718,718的內容是aaa。因此最終打印aaa。
int是基本類型,因此int只是將值複製一份給別的方法用,這個你們都知道,就不去測試了。如今看StringBuffer發生的改變。
1.初始化:
2.到change方法中:
3.發生append
4.指向新對象
這裏就要說一下了,副本指向了新對象。就比如,施瓦星格的克隆人找了另外一個女的當老婆,而真正的施瓦星格老婆沒有變。
5.回到主方法:
到這裏,StringBuffer就結束了。咱們必須知道,雖然咱們沒有去研究源碼是怎樣實現的,change方法獲得是一個sb的副本,只不過這個副本指向708,在change裏對708的對象追加,708的對象就真的改變了。而後又把sb副本指向新地址737。這只是把副本指向的地址修改了,若是你在這裏打印sb.toString(),打印的就是737裏的內容。當跳出change,回到主方法的時候,原sb仍舊仍是指向708的,最終就是打印708的結果。和String不一樣的是,StringBuffer的結果發生了變量,由於StringBuffer是可變的,能夠append。而String是不可變的,在change中s=123就是發生兩個行爲,一個是查找常量池中是否有123,若是沒有就在堆中建立123,一個是將s指向123.也就是說這時候是建立了一個新的String對象,而不是把原來的String對象s內容修改。這樣,回到主方法的時候,s仍舊是aaa。
1.初始化:
2.p傳遞到change裏的時候
3.p副本設置age
4.p副本從新賦值
這裏仍舊要說一下,p副本修改了本身指向,並不影響主方法裏的p的指向。主方法裏的p的指向沒有發生變化,依舊應該仍是720.
5.回到主方法
經過上面對String,StringBuffer,People的研究,應該明白一個道理,重要的話說三遍,重要的規則我都演示了三遍。若是跟着步驟一步步走的,確定牢記住了:
java全部的參數傳遞都是傳遞的副本,變量所表明的值的副本!java全部的參數傳遞都是傳遞的副本,變量所表明的值的副本!java全部的參數傳遞都是傳遞的副本,變量所表明的值的副本!
這裏必須記住的就是副本概念。在方法裏,運行的時候到這裏的線程都會把傳過來的參數拷貝副本帶本身的工做區中,在工做區中對這個副本的值發生一些改變。最終改變的是副本,若是經過副本的指向修改了指向中的內容,那麼那個指向的地址裏的內容確實改變了。若是修改了副本的指向,即給副本從新賦值,那麼關原來的變量何事?元變量仍舊指向最初的地址。
那麼,String傳遞過去的是副本,修改了副本的指向,打印元string是不會改變的,由於副本沒有能力修改final的String類。