1. 程式人生 > >深入理解Java中方法的參數傳遞機制

深入理解Java中方法的參數傳遞機制

static 分享 然而 深入理解java code urn 而是 基本數據類型 概念

形參和實參

我們知道,在Java中定義方法時,是可以定義參數的,比如:

public static void main(String[] args){
    
}

這裏的args就是一個字符串數組類型的參數。

在程序設計語言中,參數有形式參數和實際參數之分,先來看下它們的定義:

形式參數:是在定義函數名和函數體的時候使用的參數,目的是用來接收調用該函數時傳入的參數,簡稱“形參”。

實際參數:在主調函數中調用一個函數時,函數名後面括號中的參數稱為“實際參數”,簡稱“實參”。

舉個栗子:

public class ParamTest {
     public static void main(String[] args) {
        ParamTest pt = new ParamTest();
        // 實際參數為“張三”
        pt.sout("張三");
    }

    public void sout(String name) {
        // 形式參數為 name
        System.out.print(name);
    }   
}

上面例子中,ParamTest類中定義了一個sout方法,該方法有個String類型的參數name,該參數即為形參。在main方法中,調用了sout方法,傳入了一個參數“張三”,該參數即為實參。

那麽,實參值是如何傳入方法的呢?這是由方法的參數傳遞機制來控制的。

值傳遞和引用傳遞

參數傳遞機制有兩種:值傳遞和引用傳遞。我們先來看下程序語言中是如何定義和區分值傳遞和引用傳遞的:

值傳遞:是指在調用函數時將實際參數復制一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。

引用傳遞:是指在調用函數時將實際參數的地址傳遞到函數中,那麽在函數中對參數所進行的修改,將影響到實際參數。

那麽,在我們大Java中,到底是值傳遞還是引用傳遞呢?

Java中是值傳遞還是引用傳遞?

有了上面的概念,我們就可以一起來探究一下,Java中方法參數到底是值傳遞還是引用傳遞了。

先看如下代碼:

public class ParamPass1 {
    public static void main(String[] args) {
        ParamPass1 p = new ParamPass1();
        int i = 10;
        System.out.println("pass方法調用前,i的值為=" + i);
        p.pass(i);
        System.out.println("pass方法調用後,i的值為=" + i);
    }

    public void pass(int i) {
        i *= 3;
        System.out.println("pass方法中,i的值為=" + i);
    }
}

上面代碼中,我們在類中定義了一個pass方法,方法內部將傳入的參數i的值增加至3倍,然後分別在pass方法和main方法中打印參數的值,輸出結果如下:

pass方法執行前,i的值為=10
pass方法中,i的值為=30
pass方法執行後,i的值為=10

從上面運行結果來看,pass方法中,i的值是30,pass方法執行結束後,變量i的值依然是10。

可以看出,main方法裏的變量i,並不是pass方法裏的i,pass方法內部對i的值的修改並沒有改變實際參數i的值,改變的只是pass方法中i的值(pass方法中,i=30),因為pass方法中的i只是main方法中變量i的復制品

因此同學們很容易得出結論:Java中,一個方法不可能修改一個基本數據類型的參數 ,所以是值傳遞

然而,結論下的還太早,因為方法參數共有兩種類型:

  1. 基本數據類型
  2. 引用數據類型

前面看到的只是基本數據類型的參數,那對於引用類型的參數,又是怎麽樣的呢?看如下代碼:

public class ParamPass2 {
    public static void main(String[] args) {
        ParamPass2 p = new ParamPass2();

        User user = new User();
        user.setName("張三");
        user.setAge(18);

        System.out.println("pass方法調用前,user=" + user.toString());
        p.pass(user);
        System.out.println("pass方法調用後,user=" + user.toString());
    }

    public void pass(User user) {
        user.setName("李四");
        System.out.println("pass方法中,user = " + user.toString());
    }
}

class User {
    /**
     * 姓名
     */
    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

上面代碼中,定義了一個User類,在main方法中,new了一個新的User對象user,然後給user對象的成員變量賦值,pass方法中,修改了傳入的user對象的屬性。

運行main方法,結果如下:

pass方法調用前,user= User{name='張三', age=18}
pass方法中,user = User{name='李四', age=18}
pass方法調用後,user= User{name='李四', age=18}

經過pass方法執行後,實參的值竟然被改變了!!!那按照上面的引用傳遞的定義,實際參數的值被改變了,這不就是引用傳遞了麽?

有同學可能會說:難道在Java的方法中,在傳遞基本數據類型的時候是值傳遞,在傳遞引用數據類型的時候是引用傳遞?

其實不然,Java中傳遞引用數據類型的時候也是值傳遞

為什麽呢?

先給大家說一下概念中的重點:

值傳遞,是指在調用函數時將實際參數復制一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。

引用傳遞,是指在調用函數時將實際參數的地址直接傳遞到函數中,那麽在函數中對參數所進行的修改,將影響到實際參數。

總結下兩者的區別:

值傳遞 引用傳遞
根本區別 會創建副本 不會創建副本
所以 函數中無法改變原始對象 函數中可以改變原始對象

敲黑板:復制的是參數的引用(地址值),並不是引用指向的存在於堆內存中的實際對象。

main方法中的user是一個引用(也就是一個指針),它保存了User對象的地址值,當把user的值賦給pass方法的user形參後,即讓pass方法的user形參也保存了這個地址值,即也會引用到堆內存中的User對象。

上面代碼中,之所以產生引用傳遞的錯覺,是因為參數保存的是實際對象的地址值,你改變的只是地址值指向的堆內存中的實際對象,並沒有真正改變參數,參數的地址值沒有變。

下面結合生活中的場景,再來深入理解一下值傳遞和引用傳遞。

你有一把鑰匙,當你的朋友想要去你家的時候,如果你直接把你的鑰匙給他了,這就是引用傳遞。這種情況下,如果他對這把鑰匙做了什麽事情,比如他在鑰匙上刻下了自己名字,那麽這把鑰匙還給你的時候,你自己的鑰匙上也會多出他刻的名字。

你有一把鑰匙,當你的朋友想要去你家的時候,你復刻了一把新鑰匙給他,自己的還在自己手裏,這就是值傳遞。這種情況下,他對這把鑰匙做什麽都不會影響你手裏的這把鑰匙。

但是,不管上面哪種情況,你的朋友拿著你給他的鑰匙,進到你的家裏,把你家的電視砸了。那你說你會不會受到影響?

我們在pass方法中,改變user對象的name屬性的值的時候,不就是在“砸電視”麽。你改變的不是那把鑰匙(地址值),而是鑰匙打開的房子(地址值對應的實際對象)。

那我們如何真正的改變參數呢,看如下代碼:

public class ParamPass3 {
    public static void main(String[] args) {
        ParamPass3 p = new ParamPass3();

        User user = new User();
        user.setName("張三");
        user.setAge(18);

        System.out.println("pass方法調用前,user= " + user.toString());
        p.pass(user);
        System.out.println("pass方法調用後,user= " + user.toString());
    }

    public void pass(User user) {
        user = new User();
        user.setName("李四");
        user.setAge(20);
        System.out.println("pass方法中,user = " + user.toString());
    }
}

class User {
    /**
     * 姓名
     */
    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在這段代碼中,pass方法中,我們真正的改變了user參數,因為它指向了一個新的地址(user = new User()),即參數的地址值改變了。運行結果如下:

pass方法調用前,user= User{name='張三', age=18}
pass方法中,user = User{name='李四', age=20}
pass方法調用後,user= User{name='張三', age=18}

從結果看出,對參數進行了修改,沒有影響到實際參數。

所以說,Java中其實還是值傳遞的,只不過對於引用類型參數,值的內容是對象的引用。
技術分享圖片

深入理解Java中方法的參數傳遞機制