爲何你們都說Java中只有值傳遞?

最近跟Java中的值傳遞和引用傳遞槓上了,一度懷疑人生。查了不少資料,加上本身的理解,終於搞清楚了,什麼是值傳遞和引用傳遞。也搞明白了,爲何你們都說Java只有值傳遞,沒有引用傳遞。原來,我一直以來的認知都是錯誤的。。。segmentfault

首先,須要瞭解一些概念性的東西。ide

形參與實參:函數

形參,是指在定義函數時使用的參數,目的是用於接收調用該函數時傳入的參數。簡單理解,就是全部函數(即方法)的參數都是形參。this

實參,是指調用函數時,傳遞給函數的參數。spa

public static void main(String[] args) {
    int num = 3;
    printVal(num); //這裏num是實參
}

private static void printVal(int num) {
    num = 5; //這裏num就是形參
}

值傳遞和引用傳遞3d

值傳遞:是指在調用函數時,將實際參數複製一份傳遞給函數,這樣在函數中修改參數時,不會影響到實際參數。其實,就是在說值傳遞時,只會改變形參,不會改變實參。code

引用傳遞:是指在調用函數時,將實際參數的地址傳遞給函數,這樣在函數中對參數的修改,將影響到實際參數。對象

這裏,須要特別強調的是,千萬不要覺得傳遞的參數是值就是值傳遞,傳遞的是引用就是引用傳遞。也不要覺得傳遞的參數是基本數據類型就是值傳遞,傳遞的是對象就是引用傳遞。 這是大錯特錯的。之前的我,一直都是這樣認爲的,如今想來真是太天真了。判斷是值傳遞仍是引用傳遞的標準,和傳遞參數的類型是沒有一毛錢關係的。blog

下面三種狀況,基本上能夠涵蓋全部狀況的參數類型。內存

當傳遞的參數是基本數據類型時:

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

能夠發現,傳遞基本數據類型時,在函數中修改的僅僅是形參,對實參的值的沒有影響。

須要明白一點,值傳遞不是簡單的把實參傳遞給形參,而是,實參創建了一個副本,而後把副本傳遞給了形參。下面用圖來講明一下參數傳遞的過程:

file

圖中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對象存放在堆裏邊,其引用存放在棧裏邊,其參數傳遞圖以下:

file

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 根本不是同一個對象。畫圖理解下:
file

圖中,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中只有值傳遞,並無引用傳遞。值傳遞,不論傳遞的參數類型是值類型仍是引用類型,都會在調用棧上建立一個形參的副本。不一樣的是,對於值類型來講,複製的就是整個原始值的複製。而對於引用類型來講,因爲在調用棧中只存儲對象的引用,所以複製的只是這個引用,而不是原始對象。

最後,再次強調一下,傳遞參數是引用類型,或者說是對象時,並不表明它就是引用傳遞。引用傳遞不是用來形容參數的類型的,不要被「引用」這個詞自己迷惑了。這就如同咱們生活中說的地瓜不是瓜,而是紅薯同樣。

  1. 參數傳遞時,是拷貝實參的副本,而後傳遞給形參。(值傳遞)
  2. 在函數中,只有修改了實參所指向的對象內容,纔會影響到實參。以上第三種狀況修改的實際上只是形參所指向的對象,所以不會影響實參。
相關文章
相關標籤/搜索