最近跟Java中的值傳遞和引用傳遞槓上了,一度懷疑人生。查了不少資料,加上本身的理解,終於搞清楚了,什麼是值傳遞和引用傳遞。也搞明白了,爲何你們都說Java只有值傳遞,沒有引用傳遞。原來,我一直以來的認知都是錯誤的。。。ide
首先,須要瞭解一些概念性的東西。函數
形參與實參:this
形參,是指在定義函數時使用的參數,目的是用於接收調用該函數時傳入的參數。簡單理解,就是全部函數(即方法)的參數都是形參。code
實參,是指調用函數時,傳遞給函數的參數。對象
public static void main(String[] args) { int num = 3; printVal(num); //這裏num是實參 } private static void printVal(int num) { num = 5; //這裏num就是形參 }
值傳遞和引用傳遞blog
值傳遞:是指在調用函數時,將實際參數複製一份傳遞給函數,這樣在函數中修改參數時,不會影響到實際參數。其實,就是在說值傳遞時,只會改變形參,不會改變實參。內存
引用傳遞:是指在調用函數時,將實際參數的地址傳遞給函數,這樣在函數中對參數的修改,將影響到實際參數。字符串
這裏,須要特別強調的是,千萬不要覺得傳遞的參數是值就是值傳遞,傳遞的是引用就是引用傳遞。也不要覺得傳遞的參數是基本數據類型就是值傳遞,傳遞的是對象就是引用傳遞。 這是大錯特錯的。之前的我,一直都是這樣認爲的,如今想來真是太天真了。判斷是值傳遞仍是引用傳遞的標準,和傳遞參數的類型是沒有一毛錢關係的。get
下面三種狀況,基本上能夠涵蓋全部狀況的參數類型。class
當傳遞的參數是基本數據類型時:
public class TestNum { public static void main(String[] args) { int num = 3; System.out.println("修改前的num值:"+num); changeValue(num); System.out.println("修改後的num值:"+num); } private static void changeValue(int num) { num = 5; System.out.println("形參num值:"+num); } }
打印結果:
修改前的num值:3 形參num值:5 修改後的num值:3
能夠發現,傳遞基本數據類型時,在函數中修改的僅僅是形參,對實參的值的沒有影響。
須要明白一點,值傳遞不是簡單的把實參傳遞給形參,而是,實參創建了一個副本,而後把副本傳遞給了形參。下面用圖來講明一下參數傳遞的過程:
圖中num是實參,而後建立了一個副本temp,把它傳遞個形參value,修改value值對實參num沒有任何影響。
傳遞類型是引用類型時:
public class User { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(int age, String name) { this.age = age; this.name = name; } public User() { } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } } public class TestUser { public static void main(String[] args) { User user = new User(18, "zhangsan"); System.out.println("修改對象前:"+user); changeUser(user); System.out.println("修改對象後:"+user); } private static void changeUser(User user) { user.setAge(20); user.setName("lisi"); } }
打印結果:
修改對象前:User{age=18, name='zhangsan'} 修改對象後:User{age=20, name='lisi'}
能夠發現,傳過去的user對象,屬性值被改變了。因爲,user對象存放在堆裏邊,其引用存放在棧裏邊,其參數傳遞圖以下:
user是對象的引用,爲實參,而後建立一個副本temp,把它傳遞給形參user1。可是,他們實際操做的都是堆內存中的同一個User對象。所以,對象內容的修改也會體現到實參user上。
傳遞類型是String類型(Integer等基本類型的包裝類等同)
public class TestStr { public static void main(String[] args) { String str = new String("zhangsan"); System.out.println("字符串修改前:"+str); changeStr(str); System.out.println("字符串修改後:"+str); } private static void changeStr(String str) { str = "lisi"; } }
打印結果:
字符串修改前:zhangsan 字符串修改後:zhangsan
咦,看到這是否是感受有點困惑。按照第二種狀況,傳遞參數是引用類型時,不是能夠修改對象內容嗎,String也是引用類型,爲何在這又不變了呢?
再次強調一下,傳遞參數是引用類型,並不表明就是引用傳遞,其實它仍是值傳遞。此時的 lisi 和上邊的 zhangsan 根本不是同一個對象。畫圖理解下:
圖中,str是對象 zhangsan 的引用,爲實參,而後建立了一個副本temp,把它傳遞給了形參str1。此時,建立了一個新的對象 lisi ,形參str1指向這個對象,可是原來的實參str仍是指向zhangsan。所以,形參內容的修改並不會影響到實參內容。因此,兩次打印結果都是zhangsan。
第三種狀況和第二種狀況雖然傳遞的都是引用類型變量,可是處理方式卻不同。第三種狀況是建立了一個新的對象,而後把形參指向新對象,而第二種狀況並無建立新對象,操做的仍是同一個對象。若是把上邊changeUser方法稍做改變,你就會理解:
private static void changeUser(User user) { //添加一行代碼,建立新的User對象 user = new User(); user.setAge(20); user.setName("lisi"); }
運行以上代碼,你就會驚奇的發現,最終打印修改前和修改後的內容是如出一轍的。
這種狀況,就等同於第三種狀況。由於,這裏的形參和實參引用所指向的對象是不一樣的對象。所以,修改形參對象內容並不會影響實參內容。
修改對象前:User{age=18, name='zhangsan'} 修改對象後:User{age=18, name='zhangsan'}
總結:
從以上三個例子中,咱們就能理解了,爲何Java中只有值傳遞,並無引用傳遞。值傳遞,不論傳遞的參數類型是值類型仍是引用類型,都會在調用棧上建立一個形參的副本。不一樣的是,對於值類型來講,複製的就是整個原始值的複製。而對於引用類型來講,因爲在調用棧中只存儲對象的引用,所以複製的只是這個引用,而不是原始對象。
最後,再次強調一下,傳遞參數是引用類型,或者說是對象時,並不表明它就是引用傳遞。引用傳遞不是用來形容參數的類型的,不要被「引用」這個詞自己迷惑了。這就如同咱們生活中說的地瓜不是瓜,而是紅薯同樣。