其實(shí) Java 中是只存在值傳遞的,不存在引用傳遞。因?yàn)槲覀兇蠖鄶?shù)人是從 C 語(yǔ)言入門,而 C 語(yǔ)言中是存在引用傳遞的,所以很容易在 Java 中混淆。
你還在為開(kāi)發(fā)中頻繁切換環(huán)境打包而煩惱嗎?快來(lái)試試 Environment Switcher 吧!使用它可以在app運(yùn)行時(shí)一鍵切換環(huán)境,而且還支持其他貼心小功能,有了它媽媽再也不用擔(dān)心頻繁環(huán)境切換了。https://github.com/CodeXiaoMai/EnvironmentSwitcher
數(shù)據(jù)類型分類
Java 中的數(shù)據(jù)類型分為兩大類:基本類型 和 對(duì)象類型,相對(duì)應(yīng)的變量也分為兩類:基本類型 和 引用類型。
// int 基本類型的數(shù)據(jù)類型,num 基本類型的變量,變量的值 10 直接保存在變量 num 中。
int num = 10;
// String 對(duì)象類型的數(shù)據(jù)類型,str 引用類型的變量,str 中保存的是 "hello" 在內(nèi)存中的首地址。
String str = "hello";
下面這張圖代表了變量中保存的值,以及實(shí)際對(duì)象在內(nèi)存中的情況。

當(dāng)修改變量的值時(shí):
num = 20;
str = "java";

通過(guò)上圖可以看出:
- 對(duì)于基本類型的變量 num,賦值運(yùn)算符會(huì)直接改變它的值,原來(lái)的值被覆蓋。
- 對(duì)于引用類型的變量 str,賦值運(yùn)算符會(huì)將 str 中保存的內(nèi)存地址(“hello”的地址)改為新的地址(“java”的地址),原來(lái)的地址被覆蓋了,但是原來(lái)的地址指向的對(duì)象(“hello”)不會(huì)改變,只是沒(méi)有任何引用指向它,會(huì)在垃圾回收機(jī)制觸發(fā)時(shí)被回收。
參數(shù)傳遞
這里首先要記住一點(diǎn):參數(shù)傳遞就是賦值操作,也就是說(shuō),我們調(diào)用一個(gè)帶參的函數(shù)時(shí),形參和實(shí)參是兩個(gè)變量,在內(nèi)存中也是開(kāi)辟了兩個(gè)空間,只是把實(shí)參的值賦值給了形參。這是理解 Java 中只有值傳遞的關(guān)鍵。
下面就通過(guò)幾個(gè)例子證明這一點(diǎn)。
例1.基本類型的變量傳參

輸出結(jié)果:
99
這個(gè)很好理解,其實(shí)通過(guò)編譯器也可以發(fā)現(xiàn),foo() 方法中的 num 顏色為灰色并且?guī)в胁ɡ司€,把鼠標(biāo)放到變量上還會(huì)看到提示:“Parameter can be converted to a local variable”。意思就是這個(gè)變量可以轉(zhuǎn)化為一個(gè)本地變量,也就是在方法內(nèi)部聲明。根據(jù)“參數(shù)傳遞就是賦值操作”,來(lái)梳理一下整個(gè)過(guò)程。當(dāng)調(diào)用 foo() 方法時(shí),將實(shí)參( num) 的值賦值給了形參(num1 ),這時(shí)內(nèi)存中存在兩個(gè)變量 num(實(shí)參)、num1(形參),因?yàn)榛绢愋偷淖兞抠x值是直接覆蓋操作,所以我們對(duì)形參(num1)的操作只是改不了形參的值,而不會(huì)影響實(shí)參(num) 。
例2.普通引用類型的變量傳參
說(shuō)是普通引用類型,是指類內(nèi)部提供了改變自身的方法。

這次可以發(fā)現(xiàn)編譯器沒(méi)有提示異常信息,這至少可以證明,在 foo() 方法中對(duì)形參 myObject1 進(jìn)行操作之后,形參 myObject1 仍然被引用,那是不是可以證明形參 myObject1 和實(shí)參 myObject 是同一個(gè)對(duì)象呢?我們先看看結(jié)果:
100
結(jié)果證明,foo 方法中的實(shí)參 myObject 和形參 myObject1 確實(shí)是指向同一個(gè)對(duì)象。再根據(jù)“參數(shù)傳遞就是賦值操作”這句話,來(lái)梳理一下。當(dāng)調(diào)用 foo() 方法時(shí),將實(shí)參(myObject) 的值(引用對(duì)象的內(nèi)存地址)賦值給形參 (myObject1),這時(shí)內(nèi)存中存在兩個(gè)變量 myObject(實(shí)參)、myObject1(形參),但是它們都指向同一個(gè)內(nèi)存地址,所以我們對(duì)形參(myObject1)的操作會(huì)影響實(shí)參(myObject) 。
例3.特殊引用類型的變量傳參
上面提到了普通引用類型,當(dāng)然就存在特殊引用類型了,它是指自身保存的值不可修改。如:String 和 Integer、Double、Boolean 等基本類型的包裝類,它們都是 Immutable 類型的(它們的值都是 final 修飾的),所以每次對(duì)它們進(jìn)行賦值操作,都是創(chuàng)建一個(gè)新的對(duì)象。

我們發(fā)現(xiàn) foo() 方法中的 value 被編譯器提示可修改為本地變量。這就和例 1 中一樣在 foo 方法中對(duì)形參的修改不會(huì)影響到實(shí)參,輸出結(jié)果:
hello
這次根據(jù)“參數(shù)傳遞就是賦值操作”以及特殊引用類型的特點(diǎn)來(lái)梳理一下。當(dāng)調(diào)用 foo() 方法時(shí),將實(shí)參(value) 的值(引用對(duì)象的內(nèi)存地址)賦值給形參 (value1),這時(shí)內(nèi)存中存在兩個(gè)變量 value(實(shí)參)、value1(形參),而且它們都指向同一個(gè)內(nèi)存地址,但是當(dāng)我們對(duì)形參(value1)進(jìn)行賦值操作時(shí),因?yàn)樗且粋€(gè)自身不可修改的特殊引用類型,所以"hello"對(duì)象并沒(méi)有修改,而是在內(nèi)存中又創(chuàng)建了一個(gè)值為"java”的新對(duì)象,并把“java”的地址賦值給形參,所以只是形參變了,而實(shí)參還是指向“hello”對(duì)象。
例4. 對(duì)普通引用類型使用賦值運(yùn)算符
這次把 foo() 方法進(jìn)行了修改,在方法內(nèi)對(duì)形參進(jìn)行重新賦值操作。

這時(shí)編譯器又提示了形參 myObject 可以修改為本地變量。再根據(jù)“參數(shù)傳遞就是賦值操作”這句話,來(lái)梳理一下。當(dāng)調(diào)用 foo() 方法時(shí),將實(shí)參(myObject) 的值(引用對(duì)象的內(nèi)存地址)賦值給形參 (myObject1),這時(shí)內(nèi)存中存在兩個(gè)變量 myObject(實(shí)參)、myObject1(形參),而且它們都指向同一個(gè)內(nèi)存地址,但是在方法內(nèi)部又對(duì)形參進(jìn)行了一次賦值操作,這時(shí)形參指向了一個(gè)新的對(duì)象,而實(shí)參仍然指向原來(lái)的對(duì)象,這樣形參和實(shí)參之間沒(méi)有任何關(guān)聯(lián)了。所以我們對(duì)形參(myObject1)的操作不會(huì)影響實(shí)參(myObject) 。
其實(shí) foo() 方法等同于:
private static void foo() {
MyObject myObject = new MyObject();
myObject.num = 100;
}
同樣可以看出 foo() 方法中的形參和實(shí)參完全沒(méi)有關(guān)系了。
總結(jié)
Java 中參數(shù)傳遞其實(shí)就是賦值操作。
Java 中只存在值傳遞。
- 對(duì)于基本類型變量,是把實(shí)參的值直接復(fù)制給形參。
- 對(duì)于引用類型變量,是把實(shí)參引用的對(duì)象的內(nèi)存地址復(fù)制給形參,所以實(shí)參和形參指向同一個(gè)對(duì)象。
- 特殊引用類型變量,因?yàn)樽陨聿豢勺兊奶攸c(diǎn),當(dāng)再次對(duì)形參進(jìn)行賦值操作后,形參指向一個(gè)新的對(duì)象,而實(shí)參扔指向原來(lái)的對(duì)象。特殊引用類型變量包括 String 和一些基本類型的包裝類(Integer、Double、Boolean等)。