難倒了同事:Java 方法調用究竟是傳值仍是傳引用

Java 方法調用中的參數是值傳遞仍是引用傳遞呢?相信每一個作開發的同窗都碰到過傳這個問題,不光是作 Java 的同窗,用 C#、Python 開發的同窗一樣確定遇到過這個問題,並且頗有可能不止一次。java

那麼,Java 中究竟是值傳遞仍是引用傳遞呢,答案是值傳遞,Java 中沒有引用傳遞這個概念。程序員

數據類型和內存分配

Java 中有能夠歸納爲兩大類數據類型,一類是基本類型,另外一類是引用類型。web

基本類型shell

byte、short、int、long、float、double、char、boolean 是 Java 中的八種基本類型。基本類型的內存分配在棧上完成,也就是 JVM 的虛擬機棧。也就是說,當你使用以下語句時:json

int i = 89;
複製代碼

會在虛擬機棧上分配 4 個字節的空間出來存放。數組

引用類型app

引用類型有類、接口、數組以及 null 。咱們平時熟悉的各類自定義的實體類啊就在這個範疇裏。ide

當咱們定義一個對象而且使用 new 關鍵字來實例化對象時。學習

User user = new User();
複製代碼

會經歷以下三個步驟:this

一、聲明一個引用變量 user,在虛擬機棧上分配空間;

二、使用 new 關鍵字建立對象實例,在堆上分配空間存放對象內的屬性信息;

三、將堆上的對象連接到 user 變量上,因此棧上存儲的實際上就是存的對象在堆上的地址信息;

數組對象也是同樣的,棧上只是存了一個地址,指向堆上實際分配的數組空間,實際的值是存在堆上的。

爲了清楚的展現空間分配,我畫了一張類型空間分配的示例圖。

沒有爭議的基本類型

當咱們將 8 種基本類型做爲方法參數傳遞時,沒有爭議,傳的是什麼(也就是實參),方法中接收的就是什麼(也就是形參)。傳遞過去的是 1 ,那接到的就是1,傳過去的是 true,接收到的也就是 true。

看下面這個例子,將變量 oldIntValue 傳給 changeIntValue 方法,在方法內對參數值進行修改,最後輸出的結果仍是 1。

public static void main( String[] args ) throws Exception{
    int oldIntValue = 1;
    System.out.println( oldIntValue );
    passByValueOrRef.changeIntValue( oldIntValue );
    System.out.println( oldIntValue );
}

public static void changeIntValueint oldValue ){
    int newValue = 100;
    oldValue = newValue;
}
複製代碼

改變參數值並不會改變原變量的值,沒錯吧,Java 是按值傳遞。

數組和類

數組

有的同窗說那不對呀,你看我下面這段代碼,就不是這樣。

public static void main( String[] args ) throws Exception{
    int[] oldArray = new int[] { 12 };
    System.out.println( oldArray[0] );
    changeArrayValue( oldArray );
    System.out.println( oldArray[0] );
}

public static void changeArrayValueint[] newArray ){
    newArray[0] = 100;
}
複製代碼

這段代碼的輸出是

1
100
複製代碼

說明調用 changeArrayValue 方法時,修改傳過來的數組參數中的第一項後,原變量的內容改變了,那這怎麼是值傳遞呢。

別急,看看下面這張圖,展現了數組在 JVM 中的內存分配示例圖。

實際上能夠理解爲 changeArrayValue 方法接收的參數是原變量 oldArray 的副本拷貝,只不過數組引用中存的只是指向堆中數組空間的首地址而已,因此,當調用 changeArrayValue 方法後,就造成了 oldArray 和 newArray 兩個變量在棧中的引用地址都指向了同一個數組地址。因此修改參數的每一個元素就至關於修改了原變量的元素。

通常咱們在開發過程當中有不少將類實例做爲參數的狀況,咱們抽象出來的各類對象常常在方法間傳遞。好比咱們定義了一個用戶實體類。

public class User {

    private String name;

    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = 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 類對象,將這個實體對象傳給一個方法,這個方法可能會有一些邏輯處理,好比咱們拿到這個用戶的 name 屬性,發現 name 爲空,咱們就給 name 屬性賦予一個隨機名稱,例如 「用戶398988」。這應該是很常見的一類場景了。

咱們一般這樣使用,將 user 實例當作參數傳過來,處理完成後,再將它返回。

public static void main( String[] args ) throws Exception{
    User oldUser = new User( "原始姓名"8 );
    System.out.println( oldUser.toString() );
    oldUser = changeUserValue( oldUser );
    System.out.println( oldUser.toString() );
}

public static User changeUserValue( User newUser ){
    newUser.setName( "新名字" );
    newUser.setAge( 18 );
  return newUser;
}
複製代碼

但有的同窗說,我發現修改完成後就算不返回,原變量 oldUser 的屬性也改變了,好比下面這樣:

public static void main( String[] args ) throws Exception{
    User oldUser = new User( "原始姓名"8 );
    System.out.println( oldUser.toString() );
    changeUserValue( oldUser );
    System.out.println( oldUser.toString() );
}

public static void changeUserValue( User newUser ){
    newUser.setName( "新名字" );
    newUser.setAge( 18 );
}
複製代碼

返回的結果都是下面這樣

User{name='原始姓名', age=8}
User{name='新名字', age=18}
複製代碼

那這不就是引用傳遞嗎,改了參數的屬性,就改了原變量的屬性。仍然來看一張圖

實際上仍然不是引用傳遞,引用傳遞咱們學習 C++ 的時候常常會用到,就是指針。而這裏傳遞的實際上是一個副本,副本中只存了指向堆空間對象實體的地址而已。咱們咱們修改參數 newUser 的屬性間接的就是修改了原變量的屬性。

有同窗說,那畫一張圖說這樣就是這樣嗎,你說是副本就是副本嗎,我偏說就是傳的引用,就是原變量,也說得通啊。

確實是說的通,若是真是引用傳遞,也確實是這樣的效果沒錯。那咱們就來個反例。

public static void main( String[] args ) throws Exception{
    User oldUser = new User( "原始姓名"8 );
    System.out.println( oldUser.toString() );
    wantChangeUser( oldUser );
    System.out.println( oldUser.toString() );
}

public static void wantChangeUser( User newUser ){
    newUser = new User( "新姓名"18 );
}
複製代碼

假設就是引用傳遞,那麼 newUser 和 main 方法中的 oldUser 就是同一個引用對象,那我在 wantChangeUser 方法中從新 new 了一個 User 實體,並賦值給了 newUser,按照引用傳遞這個說法,我賦值給了參數也就是賦值給了原始變量,那麼當完成賦值操做後,原變量 oldUser 就應該是 name = "新名字"、age=18 纔對。

而後,咱們運行看看輸出結果:

User{name='原始姓名', age=8}
User{name='原始姓名', age=8}
複製代碼

結果依然是修改前的值,咱們修改了 newUser ,並無影響到原變量,顯然不是引用傳遞。

結論

Java 中的參數傳遞是值傳遞,而且 Java 中沒有引用傳遞這個概念。咱們一般說的引用傳遞,通常都是從 C 語言和 C like 而來,由於它們有指針的概念。

而咱們也知道,C、C++ 中須要程序員本身管理內存,而指針的使用常常會致使內存泄漏一類的問題,Java 千辛萬苦的就是爲了讓程序員解放出來,而使用垃圾收集策略管理內存,這其中很重要的一點就是規避了指針的使用,因此在 Java 的世界中沒有所謂的指針傳遞。

人在江湖,各位捧個贊場,輕輕點一下贊吧 👍

相關文章
相關標籤/搜索