目前兩種比較流行的方法參數傳遞模式主要是值傳遞和引用傳遞。不同的程式設計語言對于這兩種機制可能有不同的處理方式。對Java來說,一切都是嚴格按值傳遞的。
接下來,我們來一起探讨下 Java 如何為各種類型傳遞參數。在此之前,我們先解讀下值傳遞和引用傳遞的概念。
值傳遞 vs 引用傳遞
讓我們先看下函數參數傳遞的不同機制有哪些:
- 按值
- 按引用
- 按結果
- 按值-結果
- 按名稱
不過目前流行的程式設計語言中用的最多的就是按值和按引用傳遞,下面具體看下概念:
值傳遞
當一個參數作為方法的值傳遞時,被調用的方法其實是拿到了一個傳入參數值的拷貝,是以各自的參數變量的修改是互不影響的。
這意味着當你調用一個方法時,傳遞給被調用方法的參數将是原始參數的克隆。對于被調用方法的任何修改對調用方法中的源參數毫無影響。
引用傳遞
當一個參數作為方法的引用傳遞時,調用方法和被調用方法其實操作的是同一個對象。
具體來說就是這個對象的唯一辨別将會發送給被調用方法,任何對這個參數執行個體中的成員進行修改都會影響原始對象的值。
Java中的參數傳遞
在Java中,基本資料類型變量存儲的都是實際的值,但是對于非基礎資料類型(比如:類)變量存儲的是指向這個對象的位址引用。不管是值還是引用它們都存儲在棧記憶體中。
對于Java的方法參數來說總是嚴格按照值傳遞方式(copy模式)來處理的。因為在方法調用的期間,不管這個參數類型是值還是引用都會拷貝一個副本并且會在棧記憶體中開辟一個空間,然後在傳遞給目标方法。
對于基礎資料類型變量來說,就是簡單的拷貝一個值到棧記憶體,然後再傳遞給目标方法。對于非基礎資料類型變量來說,就是一個棧記憶體的引用并指向存放在堆中的實際對象,當我們傳遞這個對象時,實際傳遞的是這個對象對應的棧記憶體引用的副本。
還是以具體代碼案例來說明吧:
基礎資料類型變量傳遞
Java程式設計語言具有八種基本資料類型。基礎資料類型會直接存儲在棧記憶體中。任何基礎類型的變量不管在什麼時候作為參數傳遞時,實際的參數都會拷貝到形式參數中并且這些形式參數在棧記憶體中都有自己的獨立空間。
這些形式參數的的生命周期會僅僅在該方法運作期間存在,随着方法執行return後将從堆棧中清除并丢棄。
值傳遞案例
值傳遞棧示意圖
通過上圖可以看出,目标方法pasbyValue中對變量i進行修改後并不影響調用方法中的變量i,棧的示意圖也表明目标方法中的變量i完全是一個新的值,是以不管怎麼修改都不會影響源值。
對象類型傳遞
在Java中,所有的對象都會存儲在堆空間中。這些對象會被稱為“引用變量”的引用而引用。
與基礎類型存儲方式不一樣,Java對象會被存儲在兩個不同的地方。引用變量會被放在棧記憶體中而被引用的對象會被存儲在堆記憶體中。
每當将對象作為參數傳遞時,就會建立一個引用變量的副本并指向一個堆記憶體的對象,這個堆記憶體對象就是原始引用的對象,簡單來說就是兩個引用同時指向同一塊堆記憶體區域。
是以我們得出一個結論就是,每當我們對傳遞過來的對象進行修改時都會導緻原始對象的改變。但是,如果我們對傳遞的引用重新賦予一個新的對象,此時原始對象是不會受影響的(引用副本指向了一塊新的堆記憶體區域)。
下圖為目标方法僅僅修改對象的屬性:
引用指向同一個對象
引用指向同一個對象堆棧示意圖
下圖為目标方法為引用重新複制一個新的對象:
引用對象指向不同對象
引用對象指向不同對象的堆棧示意圖
總結
通過以上的解釋和示範,我們知道Java中的參數傳遞總是按值傳遞,對于方法執行參數結果的變化取決于傳入的是基礎資料類型還是對象:
- 對于基礎資料類型,參數按值傳遞
- 對于對象類型,對象的引用是按值傳遞,但是我們要注意按值傳遞後的應用會不會指向新的對象,這個是值得我們關注的地方