1. 程式人生 > >通過JVM記憶體模型深入理解值傳遞和引用傳遞兩種方式

通過JVM記憶體模型深入理解值傳遞和引用傳遞兩種方式

值傳遞和引用傳遞分析
Java中資料型別分為兩大類:基本型別和引用型別(也就是物件型別)。
基本型別:boolean、char、byte、short、int、long、float、double
引用型別:類、介面、陣列
因此,變數型別也可分為兩大類:基本型別和引用型別。
在分析值傳遞和引用傳遞之前,建議瞭解下以上變數型別在Java記憶體管理模型中的位置,如果對此有所瞭解,將更加有助於理解兩種傳遞的方式^_^

在Java記憶體中,基本型別變數儲存在Java棧(VM Stack)中,引用變數儲存在堆(Heap)中,模型如下:


值傳遞和引用傳遞的定義:

這裡要用實際引數和形式引數的概念來幫助理解

值傳遞:
方法呼叫時,實際引數把它的值傳遞給對應的形式引數,函式接收的是原始值的一個copy,此時記憶體中存在兩個相等的基本型別,即實際引數和形式引數,後面方法中的操作都是對形參這個值的修改,不影響實際引數的值。

引用傳遞:

也稱為傳地址。方法呼叫時,實際引數的引用(地址,而不是引數的值)被傳遞給方法中相對應的形式引數,函式接收的是原始值的記憶體地址;在方法執行中,形參和實參內容相同,指向同一塊記憶體地址,方法執行中對引用的操作將會影響到實際物件。


Demo程式碼:
package cn.roc.other;

/**
 * @author Roc
 * @desc Java中引數傳遞方式分為值傳遞和引用傳遞
* 1)基本型別變數
* @date created on 2017/12/31
 */
public class ArgumentsPassTypeTest {
    public static void main(String[] args) {
        System.out
.println(" 值傳遞測試 "); int a = 10; int b = 20; System.out.println("before swap " + "a = " + a + " b = " + b); swap(a, b); System.out.println("after swap " + "a = " + a + " b = " + b); System.out.println("-------------------------------------------------------------"
); System.out.println(" 引用傳遞測試 "); ReferenceObj obj = new ReferenceObj(); System.out.println("before swapByReference: count = " + obj.count); swapByReference(obj); System.out.println("after swapByReference: count = " + obj.count); System.out.println("-------------------------------------------------------------"); //StringCharByteShortIntegerLongFloatDoublefinal修飾的類 //對形參修改時實參不受影響 System.out.println(" final修飾的類-特殊的引用傳遞測試 "); String str = "我是final我不變"; swapByFinalClass(str); System.out.println("after swapByFinalClass, str = " + str); } /** * 值傳遞方式 基本型別 * @param a * @param b */ public static void swap(int a, int b) { int temp = a; a = b; b = temp; System.out.println("swaping " + "a = " + a + " b = " + b); } /** * 引用傳遞方式 類、陣列、介面 * @param obj */ public static void swapByReference(ReferenceObj obj) { obj.count = 0; System.out.println("swaping : count = " + obj.count); } /** * final修飾的類做形參時, 修改形參不影響實參 * @param str */ public static void swapByFinalClass(String str) { str = "我是形參"; System.out.println("swapByFinalClassing : str = " + str); } } class ReferenceObj{ int count = 99; }

輸出結果:
值傳遞測試
before swap a = 10 b = 20
swaping a = 20 b = 10
after swap a = 10 b = 20
-------------------------------------------------------------
 引用傳遞測試 
before swapByReference: count = 99
swaping : count = 0
after swapByReference: count = 0
-------------------------------------------------------------
 final修飾的類-特殊的引用傳遞測試 
swapByFinalClassing : str = 我是形參
after swapByFinalClass,  str = 我是final我不變



1)使用基本型別的變數a、b通過swap方法進行的是值傳遞,對形參修改但實參未改變,利用記憶體模型詳解原理:
 


2)使用類ReferenceObj的例項變數obj,通過swapByReference()進行的是引用傳遞的方式,具體的記憶體模型如下:


 
通過上面的分析,對於傳遞方式應該很好理解了^_^

注意:這裡要特殊考慮String,以及Integer、Double等基本型別包裝類,它們的類前面都有final修飾,為不可變的類物件,每次操作(new或修改值)都是新生成一個物件,對形參的修改時,實參不受影響,與值傳遞的效果類似,但實際上仍是引用傳遞。

總結:

1)基本型別變數作為方法中的引數,進行的值傳遞,對形參的修改不影響實參的原來的值;
2)非final修飾的類、陣列、介面作為方法中的引數,進行的引用傳遞(地址傳遞),對形參修改後實參也會改變,因為二者指向的是同一個例項;
3)final修飾的類作為方法中的引數,因為final的存在初始化後值不可變,每次操作都相當於產生一個新的例項物件,因此對形參修改時,實參也不受影響。