JAVA中的傳遞都是值傳遞嗎?有沒有引用傳遞呢?
在回答這兩個問題前,讓我們首先來看一段代碼:
- public class ParamTest {
-
- protected int num = 0;
-
-
- public void change(int i) {
- i = 5;
- }
-
-
- public void change(ParamTest t) {
- ParamTest tmp = new ParamTest();
- tmp.num = 9;
- t = tmp;
- }
-
-
- public void add(int i) {
- i += 10;
- }
-
-
- public void add(ParamTest pt) {
- pt.num += 20;
- }
-
- public static void main(String[] args) {
- ParamTest t = new ParamTest();
-
- System.out.println("參數(shù)--基本類型");
- System.out.println("原有的值:" + t.num);
-
- t.change(t.num);
- System.out.println("賦值之后:" + t.num);
-
- t.change(t);
- System.out.println("運算之后:" + t.num);
-
- System.out.println();
-
- t = new ParamTest();
- System.out.println("參數(shù)--引用類型");
- System.out.println("原有的值:" + t.num);
-
- t.add(t.num);
- System.out.println("賦引用后:" + t.num);
-
- t.add(t);
- System.out.println("改屬性后:" + t.num);
- }
- }
public class ParamTest {
// 初始值為0
protected int num = 0;
// 為方法參數(shù)重新賦值
public void change(int i) {
i = 5;
}
// 為方法參數(shù)重新賦值
public void change(ParamTest t) {
ParamTest tmp = new ParamTest();
tmp.num = 9;
t = tmp;
}
// 改變方法參數(shù)的值
public void add(int i) {
i += 10;
}
// 改變方法參數(shù)屬性的值
public void add(ParamTest pt) {
pt.num += 20;
}
public static void main(String[] args) {
ParamTest t = new ParamTest();
System.out.println("參數(shù)--基本類型");
System.out.println("原有的值:" + t.num);
// 為基本類型參數(shù)重新賦值
t.change(t.num);
System.out.println("賦值之后:" + t.num);
// 為引用型參數(shù)重新賦值
t.change(t);
System.out.println("運算之后:" + t.num);
System.out.println();
t = new ParamTest();
System.out.println("參數(shù)--引用類型");
System.out.println("原有的值:" + t.num);
// 改變基本類型參數(shù)的值
t.add(t.num);
System.out.println("賦引用后:" + t.num);
// 改變引用類型參數(shù)所指向?qū)ο蟮膶傩灾?
t.add(t);
System.out.println("改屬性后:" + t.num);
}
}
這段代碼的運行結(jié)果如下:
- 參數(shù)--基本類型
- 原有的值:0
- 賦值之后:0
- 運算之后:0
-
- 參數(shù)--引用類型
- 原有的值:0
- 賦引用后:0
- 改屬性后:20
從上面這個直觀的結(jié)果中我們很容易得出如下結(jié)論:
- 對于基本類型,在方法體內(nèi)對方法參數(shù)進(jìn)行重新賦值,并不會改變原有變量的值。
- 對于引用類型,在方法體內(nèi)對方法參數(shù)進(jìn)行重新賦予引用,并不會改變原有變量所持有的引用。
- 方法體內(nèi)對參數(shù)進(jìn)行運算,不影響原有變量的值。
- 方法體內(nèi)對參數(shù)所指向?qū)ο蟮膶傩赃M(jìn)行運算,將改變原有變量所指向?qū)ο蟮膶傩灾怠?/li>
上面總結(jié)出來的不過是我們所看到的表面現(xiàn)象。那么,為什么會出現(xiàn)這樣的現(xiàn)象呢?這就要說到值傳遞和引用傳遞的概念了。這個問題向來是頗有爭議的。
大家都知道,在JAVA中變量有以下兩種:
- 基本類型變量,包括char、byte、short、int、long、float、double、boolean。
- 引用類型變量,包括類、接口、數(shù)組(基本類型數(shù)組和對象數(shù)組)。
當(dāng)基本類型的變量被當(dāng)作參數(shù)傳遞給方法時,JAVA虛擬機(jī)所做的工作是把這個值拷貝了一份,然后把拷貝后的值傳遞到了方法的內(nèi)部。因此在上面的例子中,我們回頭來看看這個方法:
-
- public void change(int i) {
- i = 5;
- }
// 為方法參數(shù)重新賦值
public void change(int i) {
i = 5;
}
在這個方法被調(diào)用時,變量i和ParamTest型對象t的屬性num具有相同的值,卻是兩個不同變量。變量i是由JAVA虛擬機(jī)創(chuàng)建的作用域在
change(int i)方法內(nèi)的局部變量,在這個方法執(zhí)行完畢后,它的生命周期就結(jié)束了。在JAVA虛擬機(jī)中,它們是以類似如下的方式存儲的:
很明顯,在基本類型被作為參數(shù)傳遞給方式時,是值傳遞,在整個過程中根本沒有牽扯到引用這個概念。這也是大家所公認(rèn)的。對于布爾型變量當(dāng)然也是如此,請看下面的例子:
- public class BooleanTest {
-
- boolean bool = true;
-
-
- public void change(boolean b) {
- b = false;
- }
-
-
- public void calculate(boolean b) {
- b = b && false;
-
- System.out.println("b運算后的值:" + b);
- }
-
- public static void main(String[] args) {
- BooleanTest t = new BooleanTest();
-
- System.out.println("參數(shù)--布爾型");
- System.out.println("原有的值:" + t.bool);
-
- t.change(t.bool);
- System.out.println("賦值之后:" + t.bool);
-
-
- t.calculate(t.bool);
- System.out.println("運算之后:" + t.bool);
- }
- }
public class BooleanTest {
// 布爾型值
boolean bool = true;
// 為布爾型參數(shù)重新賦值
public void change(boolean b) {
b = false;
}
// 對布爾型參數(shù)進(jìn)行運算
public void calculate(boolean b) {
b = b && false;
// 為了方便對比,將運算結(jié)果輸出
System.out.println("b運算后的值:" + b);
}
public static void main(String[] args) {
BooleanTest t = new BooleanTest();
System.out.println("參數(shù)--布爾型");
System.out.println("原有的值:" + t.bool);
// 為布爾型參數(shù)重新賦值
t.change(t.bool);
System.out.println("賦值之后:" + t.bool);
// 改變布爾型參數(shù)的值
t.calculate(t.bool);
System.out.println("運算之后:" + t.bool);
}
}
輸出結(jié)果如下:
- 參數(shù)--布爾型
- 原有的值:true
- 賦值之后:true
- b運算后的值:false
- 運算之后:true
那么當(dāng)引用型變量被當(dāng)作參數(shù)傳遞給方法時JAVA虛擬機(jī)又是怎樣處理的呢?同樣,它會拷貝一份這個變量所持有的引用,然后把它傳遞給JAVA虛擬機(jī)為方法
創(chuàng)建的局部變量,從而這兩個變量指向了同一個對象。在篇首所舉的示例中,ParamTest類型變量t和局部變量pt在JAVA虛擬機(jī)中是以如下的方式存
儲的:
有一種說法是當(dāng)一個對象或引用類型變量被當(dāng)作參數(shù)傳遞時,也是值傳遞,這個值就是對象的引用,因此JAVA中只有值傳遞,沒有引用傳遞。還有一種說法是引
用可以看作是對象的別名,當(dāng)對象被當(dāng)作參數(shù)傳遞給方法時,傳遞的是對象的引用,因此是引用傳遞。這兩種觀點各有支持者,但是前一種觀點被絕大多數(shù)人所接
受,其中有《Core Java》一書的作者,以及JAVA的創(chuàng)造者James Gosling,而《Thinking in
Java》一書的作者Bruce Eckel則站在了中立的立場上。
我個人認(rèn)為值傳遞中的值指的是基本類型的數(shù)值,即使對于布爾型,雖然它的表現(xiàn)形式為true和false,但是在棧中,它仍然是以數(shù)值形式保存的,即0表
示false,其它數(shù)值表示true。而引用是我們用來操作對象的工具,它包含了對象在堆中保存地址的信息。即使在被作為參數(shù)傳遞給方法時,實際上傳遞的
是它的拷貝,但那仍是引用。因此,用引用傳遞來區(qū)別與值傳遞,概念上更加清晰。
最后我們得出如下的結(jié)論:
- 基本類型和基本類型變量被當(dāng)作參數(shù)傳遞給方法時,是值傳遞。在方法實體中,無法給原變量重新賦值,也無法改變它的值。
- 對象和引用型變量被當(dāng)作參數(shù)傳遞給方法時,在方法實體中,無法給原變量重新賦值,但是可以改變它所指向?qū)ο蟮膶傩浴V劣诘降姿侵祩鬟f還是引用傳遞,這并不重要,重要的是我們要清楚當(dāng)一個引用被作為參數(shù)傳遞給一個方法時,在這個方法體內(nèi)會發(fā)生什么。
什么叫引用?只因為這個變量的值和其它的不一樣.
首先理解:都是變量
int i;
ArrayList b;
i和b都是變量.
但i是基本變量,也叫原始變量.
其它的就叫引用變量,因為它的值是一個內(nèi)存地址值.引用對象的.但記住:它們都是有一個值的!i是一個數(shù)字,而b是一個內(nèi)存地址值(簡單的說是一個十六進(jìn)
制的值).除了基本變量之外的變量都是引用變量.Vector a;這里的a也是一個變量.它也是有值的,它的值是一個十六進(jìn)制的值.
變量的賦值:
int i=10;
int j=i;
//這里把i的值10給了j,所以j的值也是10
ArrayList b=new ArrayList();
ArrayList c=b;
//首先,b是一個引用變量,它的"值":是一個內(nèi)存地址值!!! new
ArrayList()要分配一段內(nèi)存保存它們,怎么樣找到這段內(nèi)存?那就是通過b里的值了.b的值就是new
ArrayList()所占內(nèi)存的首地址.然后c也是一個引用變量,它的值(地址值)和b是一樣的.也就是new
ArrayList()所占內(nèi)存的首地址.所以當(dāng)通過b或者c進(jìn)行操作時,它們都是操作同一個對象的.
在方法調(diào)用的時候,方法的參數(shù)實際也就是一個變量.如果是基本類型變量的時候,假設(shè)有方法method(int aa);
int j=10;
method(j);
這里邊,int aa實際也是定義了一個變量,調(diào)用的時候把j的值:10也給了aa.所以aa也是10,改變了aa的值并不會改變j的值.
如果是引用變量的時候,假設(shè)有方法methodA(ArrayList aa);
ArrayList b = new ArrayList();
methodA(b);
//方法定義了變量aa,調(diào)用的時候把b的值(地址值!!!!!)給了aa,所以aa與b有一樣的值(地址值!!!!),在方法里通過aa去操作的時候,b所引用的對象也就被改變了,因為它們引用同一個對象.
紙 a = new 銀行帳戶();//開一個銀行帳戶,返回一個卡號給你,寫在你的紙a里邊.
用一張紙(引用變量),把你的銀行卡號寫在上邊,然后調(diào)用我的時候,我用另外一張紙(引用變量---方法的形數(shù)),把你的號碼抄過來.然后我通過這個卡號,去到銀行找到你的帳號,給你存點錢.
然后你用你的紙(引用變量)上的卡號 <沒變,還是那個卡號>再去查詢銀行帳號的時候就會發(fā)現(xiàn)了多了一些錢了.....
說說我對值傳遞和引用傳遞的看法:
首先我認(rèn)為,大家對Java傳遞參數(shù)的行為是清楚的,這個爭論只是一個語義上的爭論。
也就是我們是否需要區(qū)分值傳遞和應(yīng)用傳遞呢?或者說這樣的區(qū)分有沒有意義?是否合理?
博主認(rèn)為存在引用傳遞的關(guān)鍵點在于,傳遞的對象地址值,本質(zhì)上它是一個引用,無論它是否被copy過。
認(rèn)為只有值傳遞的關(guān)鍵點在于,傳遞的對象地址值,它是一個值的copy,這個值代表的意義無所謂。
引用是c++里的概念,由于java跟c++是有一定關(guān)系的,這里把引用遷移過來,如果合理未嘗不可。
c++中關(guān)于引用的解釋一般喜歡說是看作“別名”,我查了幾本書,大部分提到引用并不會分配內(nèi)存空間,也有一本書提到,某些編譯器會分配存儲空間來存儲被引用對象的地址。
那么還是回到語義上來,c++里的這個引用,語義上是“別名”的意思,我的理解是,一組指向同一個對象的別名應(yīng)該只存儲一份內(nèi)存地址。當(dāng)然具體實現(xiàn)可能會
把引用當(dāng)做一個不可變的指針來處理(每個別名都存儲自己的對象地址)。但是請注意,我們應(yīng)該關(guān)注于它的語義,即:它沒有任何值的copy,即使是一個地
址,只是另外一個名字而已。
但是java里面沒有這樣的概念,所有的地址傳遞其行為是值的傳遞方式,語義上統(tǒng)一成值傳遞更為清晰,我們只需要考慮這個值具體是什么,無非兩種,要么是基本類型值,要么是個地址。
所以我認(rèn)為這個“引用”的概念放到j(luò)ava中并不合適。只有值傳遞的說法更合理。
posted on 2008-09-12 10:25
保爾任 閱讀(3424)
評論(1) 編輯 收藏