Java中參數(shù)傳遞是傳值還是傳引用呢滑沧?很多人遇到這個(gè)問題都會(huì)馬上給你拋出這個(gè)例子:
class Entry{
Integer value;
public Entry(Integer v){
this.value = v;
}
@Override
public String toString() {
return "Entry[value=" + value + "]";
}
}
public class CallByDemo{
public static void swap(int a,int b){
int temp = a;
a = b;
b = a;
}
public static void swap(Entry e1,Entry e2){
Integer temp = e1.value;
e1.value = e2.value;
e2.value = temp;
}
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println("before:a="+a+",b="+b);
swap(a,b);
System.out.println("after :a="+a+",b="+b);
Entry e1 = new Entry(new Integer(1000));
Entry e2 = new Entry(new Integer(2000));
System.out.println("before:e1="+e1+"e2="+e2);
swap(e1,e2);
System.out.println("after :e1="+e1+"e2="+e2);
}
}
運(yùn)行結(jié)果:
before:a=1,b=2
after :a=1,b=2
before:e1=Entry[value=1000]e2=Entry[value=2000]
after : e1=Entry [value=2000 ]e2=Entry[value=1000]
然后言之鑿鑿地拋出這個(gè)結(jié)論:
- 當(dāng)參數(shù)為基本類型時(shí)為傳值
- 當(dāng)參數(shù)為對(duì)象引用類型為傳引用
好像沒有毛病啊,但是如果我把swap(Entry e1,Entry e2)改成這樣呢膝昆?
public static void swap(Entry e1,Entry e2){
Entry temp = e1;//Integer temp = e1.value;
e1 = e2; //e1.value = e2.value;
e2 = temp;//e2.value = temp;
}
再次運(yùn)行發(fā)現(xiàn)結(jié)果變成了這樣:
before:a=1,b=2
after :a=1,b=2
before:e1=Entry[value=1000]e2=Entry[value=2000]
after : e1=Entry [value=1000 ]e2=Entry[value=2000]
什么叠必?怎么會(huì)這樣纬朝?
為了解釋這個(gè)問題收叶,我們不妨看一下Java運(yùn)行時(shí)內(nèi)存結(jié)構(gòu):
Java堆 (Java Heap)
- 作用:存放幾乎所有的對(duì)象實(shí)例和數(shù)組
- 組成
- 新生代(Young Generation)
- Eden區(qū):存放新創(chuàng)建的對(duì)象或短期的對(duì)象
- Survivor區(qū):存放GC后的幸存的或中期的對(duì)象
- 老年代(Old Generation):存放GC多次后始終存在或者長(zhǎng)期的對(duì)象及Survivor區(qū)放不下的大對(duì)象
永久代(Permanent Generation):永久代在JDK8中被完全地移除
- 新生代(Young Generation)
- 是否線程共享:是
Java虛擬機(jī)棧(JVM Stacks)
- 作用:存放棧幀
- 組成:棧幀
- 是否線程共享:線程私有的,生命周期和線程的相同
棧幀(Stack Frame)
- 作用:方法在執(zhí)行的時(shí)候澄峰,都會(huì)有一個(gè)棧幀創(chuàng)建出來俏竞,用于存儲(chǔ)局部變量表、操作數(shù)棧堂竟、動(dòng)態(tài)鏈接、方法出口等信息
- 組成:
- 局部變量表(Local Variables):存放編譯時(shí)可知的各種基本數(shù)據(jù)類型跃捣、對(duì)象引用
- 操作數(shù)棧(Operand Stacks):供方法調(diào)用時(shí)進(jìn)行各種運(yùn)算
- 動(dòng)態(tài)鏈(Dynamic Linking):
- 方法出口
方法區(qū)(Method Area)
- 作用:存放被虛擬機(jī)加載的類的結(jié)構(gòu)信息(如:字段和方法數(shù)據(jù)、方法的字節(jié)碼疚漆、運(yùn)行時(shí)常量池等)、常量刁赦,靜態(tài)變量及類、實(shí)例甚脉、接口初始化時(shí)用到的特殊方法丸升。
- 組成:方法區(qū)是堆的邏輯組成部分(有人稱之為永久代 Permanent Generation)
- 是否線程共享:是
本地方法棧(Native Method Stacks)
- 作用:存放本地方法調(diào)用時(shí)的棧幀
- 組成:棧幀
- 是否線程共享:線程私有的,生命周期和線程的相同
- 虛擬機(jī)執(zhí)行Native方法時(shí)使用牺氨,不同的虛擬機(jī)有不同的實(shí)現(xiàn)方法墩剖,HotSpot虛擬機(jī)的本地方法棧和虛擬機(jī)棧合二為一。
PC寄存器/程序計(jì)數(shù)器(pc Register)
- 作用:保存JVM正在執(zhí)行方法的字節(jié)碼指令的地址,如果該方法為native本地方法則為undefined
- 組成:一塊至少能夠保存一個(gè)本地指針或者returnAddress的值的內(nèi)存空間
- 是否線程共享:每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線程之間計(jì)數(shù)器互不影響沼头,獨(dú)立存儲(chǔ)
OK进倍,我們?cè)賮矸治鲆幌律厦娴膯栴}:
其實(shí)呢,Java采用的是傳值(call by value)猾昆,形參只是實(shí)際參數(shù)的一個(gè)拷貝毡庆,形參不能修改實(shí)參的內(nèi)容。
當(dāng)值為基本數(shù)據(jù)類型時(shí)么抗,swap(int,int)方法中的局部變量a,b接收傳入的值并保存在與該方法對(duì)應(yīng)的棧幀的局部變量表中毅否。而main方法中的a,b保存在main方法對(duì)應(yīng)的棧幀的局部變量表中,修改swap方法中的a,b對(duì)main方法中的a,b沒有任何影響蝇刀,所以交換失敗螟加。
當(dāng)值為引用類型時(shí),傳入方法的也是它的一個(gè)拷貝吞琐,當(dāng)然這個(gè)拷貝有點(diǎn)特殊捆探,它是Java Heap中的對(duì)象(Entry_e1、Entry_e2)的一個(gè)引用站粟。該引用也保存在對(duì)應(yīng)的棧幀的局部變量表中黍图,修改swap方法中的e1,e2的引用指向?qū)ain方法中的e1,e2沒有任何影響,所以交換失敗奴烙。但局部變量e1,e2可以通過引用改變Heap中的對(duì)象的狀態(tài)助被,如第一段代碼中在swap中的局部變量可以通過引用來修改Heap中的對(duì)象的value屬性,從而達(dá)到交換屬性中的目的切诀。
此外揩环,需要注意的是Java中的某些類如:String、基本類型的包裝類幅虑、BigInteger丰滑、BigDecimal是不可變的,即無法修改其內(nèi)容倒庵。
最后總結(jié)一句:Java是方法調(diào)用是值傳遞褒墨!