先說結(jié)論给僵,Java是值傳遞
很多小伙伴認為:值傳遞和引用傳遞區(qū)分的條件就是毫捣,如果方法的實參傳遞的是一個值,則是值傳遞帝际,如果傳遞的是一個引用蔓同,則是引用傳遞,或者說蹲诀,如果傳遞的是一個基本數(shù)據(jù)類型斑粱,則是值傳遞,如果傳遞的是一個對象脯爪,則是引用傳遞则北。
但是這種觀點是錯誤的矿微,首先讓我們來看一下值傳遞和引用傳遞的定義:
值傳遞是指在調(diào)用函數(shù)時將實際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對這個參數(shù)進行修改尚揣,將不會影響到實際參數(shù)
引用傳遞是指在調(diào)用函數(shù)時將實際參數(shù)的地址直接傳遞到函數(shù)中涌矢,那么在函數(shù)中對該參數(shù)所進行的修改,將影響到實際參數(shù)
有了定義快骗,我們可以做實驗了:
public static void main(String[] args) {
Test test = new Test();
int value = 100;
test.alterAndPrint(value);
System.out.println("main函數(shù)中value的值為: " + value);
}
public void alterAndPrint(int i) {
i = 500;
System.out.println("alterAndPrint函數(shù)中value的值為: " + i);
}
這段代碼的輸出結(jié)果為:
alterAndPrint函數(shù)中value的值為: 500
main函數(shù)中value的值為: 100
有了這個結(jié)果娜庇,很多小伙伴就覺得可以下結(jié)論了:Java是值傳遞,但是可能會有人說不對方篮,然后擺出另一個實驗結(jié)果:
public static void main(String[] args) {
Test test = new Test();
Person person = new Person();
person.setName("zhangsan");
person.setAge(18);
test.alterAndPrint(person);
System.out.println("main函數(shù)中的Person為: " + person);
}
public void alterAndPrint(Person p) {
p.setName("lisi");
p.setAge(24);
System.out.println("alterAndPrint函數(shù)中的Person為: " + p);
}
結(jié)果為:
alterAndPrint函數(shù)中的Person為: Person{name='lisi', age=24}
main函數(shù)中的Person為: Person{name='lisi', age=24}
然后發(fā)現(xiàn)名秀,實參的內(nèi)容發(fā)生了改變,于是就得出結(jié)論恭取,Java在傳遞基本數(shù)據(jù)類型的時候是值傳遞泰偿,在傳遞對象的時候是引用傳遞熄守,但是還有一種特殊情況:
public static void main(String[] args) {
Test test = new Test();
String s = "Java";
test.alterAndPrint(s);
System.out.println("main函數(shù)中的String為: " + str);
}
public void alterAndPrint(String str) {
str = "Golang";
System.out.println("alterAndPrint函數(shù)中的String為: " + str);
}
這段代碼結(jié)果為:
alterAndPrint函數(shù)中的String為: Golang
main函數(shù)中的String為: Java
好蜈垮,走到這一步,已經(jīng)有很多小伙伴開始疑惑了裕照,不是傳遞對象的時候是引用傳遞嗎攒发,為什么又變成值傳遞了。
其實晋南,概念上是沒有問題的惠猿,但是我們的實驗方法出了些許問題,我們再來回看定義:
值傳遞是指在調(diào)用函數(shù)時將實際參數(shù)復(fù)制一份傳遞到函數(shù)中负间,這樣在函數(shù)中如果對這個參數(shù)進行修改偶妖,將不會影響到實際參數(shù)
引用傳遞是指在調(diào)用函數(shù)時將實際參數(shù)的地址直接傳遞到函數(shù)中,那么在函數(shù)中對該參數(shù)所進行的修改政溃,將影響到實際參數(shù)
重點區(qū)分一下這兩個的區(qū)別就是:
值傳遞需要復(fù)制一份參數(shù)趾访,且函數(shù)中對參數(shù)的改變不會影響到原參數(shù)
引用傳遞不需要復(fù)制,且函數(shù)中對該參數(shù)的改變會影響到原參數(shù)
在上面的Person那個例子中董虱,我們改變的是參數(shù)的內(nèi)容扼鞋,而不是參數(shù)本身,所以說本身方法就錯了愤诱,正常的實驗方法云头,也即是真正地去改變參數(shù)應(yīng)該是這樣:
public static void main(String[] args) {
Test test = new Test();
Person person = new Person();
person.setName("zhangsan");
person.setAge(18);
test.alterAndPrint(person);
System.out.println("main函數(shù)中的Person為: " + person);
}
public void alterAndPrint(Person p) {
p = new Person();
p.setName("lisi");
p.setAge(24);
System.out.println("alterAndPrint函數(shù)中的Person為: " + p);
}
輸出結(jié)果為:
alterAndPrint函數(shù)中的Person為: Person{name='lisi', age=24}
main函數(shù)中的Person為: Person{name='zhangsan', age=18}
配合圖來理解:
在main函數(shù)中我們new了一個person對象,并且給它的name和age屬性賦值淫半,person就擁有了這個內(nèi)存的地址溃槐,也即是0x777999,當(dāng)調(diào)用alterAndPrint方法時科吭,將實參person傳給形參p竿痰,p也指向了這一塊地址脆粥,執(zhí)行alterAndPrint方法的內(nèi)容時,對參數(shù)進行修改影涉,也即p = new Person()变隔,這行代碼會在堆區(qū)開辟一段新的內(nèi)存,后面對p的更改都不會影響到0x777999這塊內(nèi)存的數(shù)據(jù)蟹倾,
上面這種方式是什么傳遞匣缘,反正肯定不是引用傳遞,因為如果是引用傳遞的話鲜棠,在p = new Person()這一串代碼執(zhí)行完后肌厨,實際的參數(shù)的引用也應(yīng)該指向0x777555這一塊內(nèi)存,但是我們發(fā)現(xiàn)并沒有這樣
通過上面的定義我們可以知道豁陆,這是把實際參數(shù)的引用地址復(fù)制了一份柑爸,傳遞給了形式參數(shù),所以盒音,上面的參數(shù)其實是值傳遞表鳍,把實參對象引用的地址當(dāng)做值傳遞給了形式參數(shù)。
所以祥诽,要區(qū)分值傳遞和引用傳遞并不是靠辨別傳遞的內(nèi)容譬圣,而是有沒有將實參復(fù)制一份副本傳遞給形參,如果傳遞的是地址雄坪,那么就要看這個地址是否發(fā)生改變厘熟,而不是看地址對應(yīng)的對象的內(nèi)容是否發(fā)生改變
那為什么String的那個例子會出現(xiàn)另一種情況呢?
因為str = "Golang"這一句代碼改變了引用的地址维哈,new了一個新的String绳姨,也即等價于str=new String("Golang'),而并沒有改變原實參的引用地址