標籤:
以前學習C#的時候,是完全在工作崗位上學習,一些底層較為深入的道理都不是很清楚。如今學習了Java,對於Java參數傳遞只能傳值,不能傳引用(指標)感到很困惑,在C#中不是常常說把某個引用傳遞到函數中嗎?甚至C#有相當簡便的ref、out參數,明明白白的傳引用。
經過一番探索,得出的結論表明,Java中我不管你到底是傳值還是傳引用,只需要記住原生資料類型(實值型別)和String作為參數傳遞的時候,其原本的值都不會發生改變;而參考型別在作為參數傳遞時,函數中對其的操作,都會反饋到引用所指向的值。
這個結論不是和C#一模一樣嘛,除了C#可以方便的使用ref、out以外……
下面對Java中參數傳遞只能傳值進行解釋,事實證明,以前我對於C#的傳值還是傳引用理解的也不夠深入:
先申明:java中的參數傳遞都是值傳遞,沒有引用傳遞。
值傳遞的概念:值傳遞會為所傳遞的對象(這裡的對象不是java裡的對象object,而是一般意義上的對象)重新開闢一塊空間,於是對所傳對象的操作不會影響到原對象。
現象:有種說法是java中基礎資料型別 (Elementary Data Type)作參數是值傳遞,對象做參數是引用傳遞。實際上是錯誤的,真正的引用傳遞,是類似C語言中的指標傳遞,和C#中的ref參數。
理解:為什麼對象做參數也是值傳遞呢?
分析:對象做參數傳遞的是對象的引用,暫且可以理解成對象的地址。由於是值傳遞,那麼被傳遞的對象本身是不會有任何變化的,所以該對象的引用是不會發生任何變化的。
1、先舉一個原生資料類型的例子:
測試函數:
public static void ValueTrans(int a){ a = 2;//新產生一個值,開闢一個實值型別出來,故值不會被修改。 }
測試此函數的代碼:
int a = 1; System.out.println("a‘s value(before) = " + a); ValueTrans(a); System.out.println("a‘s value(after) = " + a);
列印出的結果:
a‘s value(before) = 1a‘s value(after) = 1
安裝Java值傳遞的思想來考慮,這個結果是顯而易見的,pass by value,我只是把這個數值傳給你使用。
2、那麼Integer如何理解呢?
測試函數:
public static void ValueTrans2(Integer a){ a = new Integer(2); }
其中a也是一個對象,那麼這麼傳入,是不是傳引用呢?調用了這個函數,a的int值會不會被修改呢?
測試此函數的代碼:
Integer a2 = new Integer(1); System.out.println("a2‘s value(before) = " + a2); ValueTrans2(a2); System.out.println("a2‘s value(after) = " + a2);
列印出的結果:
a2‘s value(before) = 1a2‘s value(after) = 1
結果表明,值仍舊沒有被修改。
如何解釋這個現象:
理解這裡的關鍵是區分對象和引用。這裡聲明的a2是一個引用,而不是一個對象(只是Java把它設計為看上去好像是對象一樣)。這個引用它指向了一個對象,這個對象就是後面用new關鍵字產生的對象。因此,可以說a2指向了一個Integer對象。
在調用ValueTrans2方法的時候,程式將a2作為參數傳遞給ValueTrans2方法了。這裡仍然是值傳遞,在ValueTrans2調用過程中,會產生一份新的引用(不妨叫做y)。此時,a2和y指向了同一個對象。
但是此時,我們讓y指向另外一個對象, y=new Integer(2); 此時a2和y就指向了不同的對象。y修改了它指向的對象的屬性,很顯然不會影響到a2指向的對象。
(這裡不妨說明一下參考型別的傳遞,如我把一個參考型別x傳入函數中,這裡也是值傳遞,在函數被調用的過程中,產生了一個新的引用y,x和y指向同一個對象。此時修改y指向的對象,也即修改x所指向的對象!那麼參考型別的傳遞,會造成x所指向的值被修改)
圖解如下:
3、如何理解String?
如果你理解了2當中Integer的傳遞方式,那麼我想String也不難理解。我們建立一個String,
String string = "old String";
實際上就是
String string2 = new String("old String");
Java的機制,造成當你使用一個String如"old String"時,會建立一個對象出來。
本著負責任的態度,仍舊把相應的測試函數貼上來:
public static void StringTrans(String string){ string = "new String"; }
測試函數的代碼:
String string = "old String"; System.out.println("string‘s value(before) = " + string); StringTrans(string); System.out.println("string‘s value(after) = " + string);
列印出的結果:
string‘s value(before) = old Stringstring‘s value(after) = old String
原因就是,實際上函數StringTrans是複製了一個引用指向原來的對象,但是函數又讓這個引用指向了新的String對象。這不會對原來的引用和原來的對象造成任何影響!
4、如何理解參考型別是傳值?
測試自訂類:
public class People { public int age; public String name; public People(int age, String name){ this.age = age; this.name = name; }}
測試的函數:
public static void ReferTrans(People people){ people.age = 2; people.name = "lisi"; }
測試函數的代碼:
People people = new People(1, "zhangsan"); System.out.println("people‘s age(before) = " + people.age); System.out.println("people‘s name(before) = " + people.name); ReferTrans(people); System.out.println("people‘s age(after) = " + people.age); System.out.println("people‘s name(after) = " + people.name);
列印的結果:
people‘s age(before) = 1people‘s name(before) = zhangsanpeople‘s age(after) = 2people‘s name(after) = lisi
設建立的對象是PEOPLE,其引用是people,在調用ReferTrans方法是,仍然是值傳遞,方法賦值了一個引用people2,people2仍舊指向PEOPLE,所以操作people2對對象進行修改,即為對PEOPLE進行修改(String的話在此時已經new了一個新對象出來),和使用people的效果一樣。表現出現來現象就是,當參考型別傳入函數時,函數中對其的修改,都會表現在原來的參考型別上。
圖解如下:
我一直認為只給分析過程,不給結論的都是耍流氓的行為。
結論:
1、在實際的使用過程中,我們不必糾結那麼多,原生資料類型和String,在傳入函數的時候,不管函數裡面做什麼,他的值都不會被修改。參考型別傳遞到函數中,只要函數中不是讓參數指向一個建立的對象,那麼函數中的修改會反映到此參考型別上。
2、平時的處理上,可以把String和原生資料類型放在同一類的情況下使用,因為Java內部使用了很多的機制,讓String看起來跟原生資料類型一樣。但是String歸根結底是一個參考型別,如在進行比較時,其他原生資料類型可以使用==,而String必須使用equals方法才能比較其內部的值(這點C#做的非常好,直接使用==,不讓程式員過分的瞭解底層,我在做C#編程的過程中,甚至從來都沒有覺得string和實值型別有什麼不同)。
3、Java確實是只能傳值,不能傳引用。所謂傳引用,只在C、C++的傳指標和C#的ref中存在。
如何理解Java中參數傳遞只能傳值?