JAVA到底是值傳遞還是引用傳遞?
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
Java到底是值傳遞還是引用傳遞呢?可能我們背過很多次,說java是值傳遞(當然,網(wǎng)上也有一些同學信誓旦旦的說java是引用傳遞,錯誤),但是當我突然再問你java為什么是值傳遞的?那可能就有點懵了。
1.形參和實參
JAVA是一種面向?qū)ο蟮木幊陶Z言,一個類中有屬性和方法,我們這里重點說下方法的定義。
package com.donkey;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int changeAge(int age) {
age = age+1;
System.out.println("changeAge方法執(zhí)行后的年齡:" + age);
return age;
}
public static void main(String[] args) {
Person person = new Person();
person.changeAge(10);
}
}
我們重點看一下changeAge(int age)這個方法,這是一個有參的方法,在main方法中調(diào)用changeAge方法,并且傳遞參數(shù)age =1;
說到這里,我要引出兩個概念:形式參數(shù)(形參)和實際參數(shù)(實參);
形參:是在定義函數(shù)名和函數(shù)體的時候使用的參數(shù),目的是用來接收調(diào)用該函數(shù)時傳入的參數(shù);
實參:在調(diào)用有參函數(shù)時,主調(diào)函數(shù)和被調(diào)函數(shù)之間有數(shù)據(jù)傳遞關(guān)系。在主調(diào)函數(shù)中調(diào)用一個函數(shù)時,函數(shù)名后面括號中的參數(shù)稱為“實際參數(shù)”。
如上面的例子中,changeAge(int age)這個方法中的age就是形參,而調(diào)用這個方法時傳遞的10就是實參。
即:實參是調(diào)用有參方法的時候真正傳遞的內(nèi)容,而形參是用于接收實參內(nèi)容的參數(shù)。
2.值傳遞與引用傳遞
通過上面,我們知道了形參和實參,那么在調(diào)用方法的時候,我們怎么傳遞的呢?或者說傳遞的是什么呢?這就引出了下面的概念:值傳遞和引用傳遞。
值傳遞:調(diào)用函數(shù)時將實際參數(shù)復制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對參數(shù)進行修改,將不會影響到實際參數(shù)。
引用傳遞:指在調(diào)用函數(shù)時將實際參數(shù)的地址直接傳遞到函數(shù)中,那么在函數(shù)中對參數(shù)所進行的修改,將影響到實際參數(shù)。
基于上面的理論知識,我們看下面這段程序:
public static void main(String[] args) {
Person person = new Person();
int age = 20;
person.changeAge(age);
System.out.println("main方法中的年齡:" + age);
}
輸出的結(jié)果為:
changeAge方法執(zhí)行后的年齡:21
main方法中的年齡:20從結(jié)果可知,changeAge方法并沒有改變實際參數(shù)age的值,實參age還是等于20。所以我們得出結(jié)論:java是值傳遞。
但真實情況可能并不是這么簡單,因為我們傳遞是一個基本數(shù)據(jù)類型,那如果是傳一個引用類型的實參呢?那再看下面的小程序:
public String changeName(String name) {
name = "zhangsan";
System.out.println("changeName方法執(zhí)行后的名字:" + name);
return name;
}
public static void main(String[] args) {
Person person = new Person();
String name ="lisi";
person.changeName(name);
System.out.println("main方法中的name:" + name);
}
輸出結(jié)果如下:
changeName方法執(zhí)行后的名字:zhangsan
main方法中的name:lisi
顯然,changeName方法并沒有改變實參name的值,main方法中的name還是lisi。所以:java是值傳遞。
可能你說,你這個實驗的不對,要是傳遞一個對象,就不是這樣的了,那么我們以user為例,再看下面的小程序:
public void changPerson(Person person) {
person.setAge(30);
person.setName("zhangfei");
System.out.println("changPerson方法執(zhí)行后的person::" + person);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person();
person.setAge(45);
person.setName("gunayu");
person.changPerson(person);
System.out.println("main方法中的person:" + person);
}
執(zhí)行結(jié)果如下:
changPerson方法執(zhí)行后的person::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘zhangfei’, age=‘30’}
哦哦哦,這時候你可能會說,看看吧,person的內(nèi)容改變了,這說明java在傳遞對象的時候是引用傳遞。那么這個結(jié)論正確么?如果這個結(jié)論不正確,那它錯在哪里呢?
3.JAVA中的值傳遞
通過上面的三個小程序,我們得出來了不同的結(jié)論,這也是我當初怎么也無法理解這個概念的原因。其實上面的理論沒有問題,只是我們實驗的方法由問題。通過上面值傳遞和引用傳遞的概念,我們知道,這兩個的概念的最核心的區(qū)別就是值傳遞過程中或不會重新復制一份副本出來。

我們再來舉個比較形象的例子。
當你的同事想開你的車出差,然后你有一把車鑰匙,你直接把這把車鑰匙給了同事。假如這個同時給車鑰匙加了一個鑰匙套,那么當同事把車鑰匙還給你的時候,這把鑰匙就會包裹了一個鑰匙套,這就是引用傳遞。
當你的同事想開你的車出差,你有一把車鑰匙,然后你找4S店復制了一把車鑰匙,你把新復制的這把車鑰匙給了同事。假如這個同時給車鑰匙加了一個鑰匙套,那么當同事把車鑰匙還給你的時候,你手里的這把原始鑰匙依然沒有鑰匙套,這就是值傳遞。
下面我們再看看一個小程序:
public void changPerson1(Person person) {
person = new Person();
person.setAge(30);
person.setName("zhangfei");
System.out.println("changPerson1::" + person);
}
public static void main(String[] args) {
Person guanyu = new Person();
guanyu.setAge(45);
guanyu.setName("gunayu");
guanyu.changPerson1(guanyu);
System.out.println("main方法中的person:" + guanyu);
}
執(zhí)行結(jié)果如下:
changPerson1::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘gunayu’, age=‘45’}
當我們在main中創(chuàng)建一個Person對象的時候,在堆中開辟一塊內(nèi)存,其中保存了name和age數(shù)據(jù)。然后guanyu持有該內(nèi)存的地址0x00000001

當嘗試調(diào)用changPerson1方法,并且guanyu作為實際參數(shù)傳遞給形式參數(shù)person的時候,會把這個地址0x00000001交給person,這時,person也指向了這個地址

然后在changPerson1方法內(nèi)對參數(shù)進行修改的時候,即person= new Person();,會重新開辟一塊0x00000002的內(nèi)存,賦值給person。后面對user的任何修改都不會改變內(nèi)存0x00000001`的內(nèi)容,

上面這種傳遞是什么傳遞?肯定不是引用傳遞,如果是引用傳遞的話,在執(zhí)行person= new Person();的時候,實際參數(shù)的引用也應(yīng)該改為指向0x00000001,但是實際上并沒有。
所以,java在傳遞對象時,傳遞是堆中的那一小塊內(nèi)存區(qū)域,而并不是person這個引用的本身。
**所以,值傳遞和引用傳遞的區(qū)別并不是傳遞的內(nèi)容。而是實參到底有沒有被復制一份給形參。**在判斷實參內(nèi)容有沒有受影響的時候,要看傳的的是什么,如果你傳遞的是個地址,那么就看這個地址的變化會不會有影響,而不是看地址指向的對象的變化。就像車鑰匙和車的關(guān)系,車鑰匙通過引用執(zhí)行車,所以如果是引用傳遞的話,變化的應(yīng)該是車鑰匙而不是引用的那輛車。
其實也好理解,guanyu = new Person(),其實完全就可以把那小塊對空間理解成guanyu所對應(yīng)的值。
4.小結(jié)
無論是值傳遞還是引用傳遞,其實都是一種求值策略(Evaluation strategy)。在求值策略中,還有一種叫做按共享傳遞(call by sharing)。其實Java中的參數(shù)傳遞嚴格意義上說應(yīng)該是按共享傳遞。
按共享傳遞,是指在調(diào)用函數(shù)時,傳遞給函數(shù)的是實參的地址的拷貝(如果實參在棧中,則直接拷貝該值)。在函數(shù)內(nèi)部對參數(shù)進行操作時,需要先拷貝的地址尋找到具體的值,再進行操作。如果該值在棧中,那么因為是直接拷貝的值,所以函數(shù)內(nèi)部對參數(shù)進行操作不會對外部變量產(chǎn)生影響。如果原來拷貝的是原值在堆中的地址,那么需要先根據(jù)該地址找到堆中對應(yīng)的位置,再進行操作。因為傳遞的是地址的拷貝所以函數(shù)內(nèi)對值的操作對外部變量是可見的。
即:Java中的傳遞,是值傳遞,如果是引用類型這個值,實際上傳遞的是對象的引用(即棧與對之間的那條指向的線,每次傳遞時,其實是復制出了這樣一條線,只不過線的起點換成了形參,終點沒有變)。

————————————————
版權(quán)聲明:本文為CSDN博主「泗水長流」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/lvxinchun/article/details/116140524
粉絲福利:Java從入門到入土學習路線圖
??????

??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
