關於Java物件作為引數傳遞是傳值還是傳引用的問題
前言
在Java中,當物件作為引數傳遞時,究竟傳遞的是物件的值,還是物件的引用,這是一個飽受爭議的話題。若傳的是值,那麼函式接收的只是實參的一個副本,函式對形參的操作並不會對實參產生影響;若傳的是引用,那麼此時對形參的操作則會影響到實參。
首先我們來看一句程式碼:
Object obj = new Object();
這句話的意思是:建立一個Object物件,再建立一個名為obj的引用,讓這個引用指向這個物件,如下圖所示:
在有了上面的基礎之後,我們便來看下面這組在網上很流行的例子:
基本資料型別作為引數傳遞:
例1:
public class test { public static void main(String[] args) { int i = 1; System.out.println("before change, i = "+i); change(i); System.out.println("after change, i = "+i); } public static void change(int i){ i = 5; } }
這個例子不難理解,當基本資料型別作為引數傳遞時,傳遞的是實參值的副本,即傳的是值,無論在函式中怎麼操作這個副本,實參的值是不會被改變的。所以以上程式碼執行的結果是:
before change, i = 1
after change, i = 1
物件作為引數傳遞:
在下面的例2中,我們把StringBuffer物件作為引數傳遞到change函式。
例2:
public class test { public static void main(String[] args) { StringBuffer sb = new StringBuffer("Hello "); System.out.println("before change, sb is "+sb.toString()); change(sb); System.out.println("after change, sb is "+sb.toString()); } public static void change(StringBuffer stringBuffer){ stringBuffer.append("world !"); } }
為了方便推理出結論,我們先直接看程式的執行結果:
before change, sb is Hello
after change, sb is Hello world !
從輸出結果中我們可以發現,sb所指向的物件的值被改變了,那麼是否我們可以推論出,在Java中,當物件作為引數傳遞時,傳遞的是該物件的引用呢?我們再來看下面這個例子:
例3:
public class test { public static void main(String[] args) { StringBuffer sb = new StringBuffer("Hello "); System.out.println("before change, sb is "+sb.toString()); change(sb); System.out.println("after change, sb is "+sb.toString()); } public static void change(StringBuffer stringBuffer){ stringBuffer = new StringBuffer("Hi "); stringBuffer.append("world !"); } }
如果上面的推論是正確的,即Java中物件作為引數傳遞,實際傳遞的是該物件的引用,那麼在呼叫change函式之後,原物件的值應該是會改變的,變為“Hi world !”,但是,當我們執行程式後,結果卻是如下所示:
before change, sb is Hello
after change, sb is Hello
原物件的值並沒有被改變,這與上面的推論相矛盾!為什麼在Java中,當物件作為引數傳遞時,有的時候實參被改變了,而有的時候實參並未被改變呢?下面讓我們來分析一下其中的原因:
從文章的開頭我們知道,當執行StringBuffer sb = new StringBuffer(“Hello “)時,我們建立了一個指向新建物件“new StringBuffer(“Hello “)”的引用“sb”,如下圖所示:
在例2中,當我們呼叫change函式後,實際上,形參stringBuffer也指向了實參sb所指向的物件,即:
那麼當我們執行stringBuffer.append(“world !”)後,便通過物件的引用“stringBuffer”修改了物件的值,使之變成了“Hello world !”,即:
但是,在例3中的change函式中,我們又新建了一個物件“new StringBuffer(“Hi “)”(這實際上在記憶體中開闢了一塊在原物件地址之外的新區域),這讓形參stringBuffer實際指向了這個新建的物件,並將新物件的值設定為“Hi world !”,即:
那麼我們就不難理解,為何在執行完change函式之後,實參的值仍為“Hello”了。
結論
綜上所述,我們可以得出結論:在Java中,當物件作為引數傳遞時,實際上傳遞的是一份“引用的拷貝”。