通過JVM記憶體模型深入理解值傳遞和引用傳遞兩種方式
阿新 • • 發佈:2019-01-28
值傳遞和引用傳遞分析
Java中資料型別分為兩大類:基本型別和引用型別(也就是物件型別)。
基本型別:boolean、char、byte、short、int、long、float、double
引用型別:類、介面、陣列
因此,變數型別也可分為兩大類:基本型別和引用型別。
在分析值傳遞和引用傳遞之前,建議瞭解下以上變數型別在Java記憶體管理模型中的位置,如果對此有所瞭解,將更加有助於理解兩種傳遞的方式^_^
方法呼叫時,實際引數把它的值傳遞給對應的形式引數,函式接收的是原始值的一個copy,此時記憶體中存在兩個相等的基本型別,即實際引數和形式引數,後面方法中的操作都是對形參這個值的修改,不影響實際引數的值。
引用傳遞:
也稱為傳地址。方法呼叫時,實際引數的引用(地址,而不是引數的值)被傳遞給方法中相對應的形式引數,函式接收的是原始值的記憶體地址;在方法執行中,形參和實參內容相同,指向同一塊記憶體地址,方法執行中對引用的操作將會影響到實際物件。
Demo程式碼:
輸出結果:
值傳遞測試
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方法進行的是值傳遞,對形參修改但實參未改變,利用記憶體模型詳解原理:
通過上面的分析,對於傳遞方式應該很好理解了^_^
注意:這裡要特殊考慮String,以及Integer、Double等基本型別包裝類,它們的類前面都有final修飾,為不可變的類物件,每次操作(new或修改值)都是新生成一個物件,對形參的修改時,實參不受影響,與值傳遞的效果類似,但實際上仍是引用傳遞。
總結:
1)基本型別變數作為方法中的引數,進行的值傳遞,對形參的修改不影響實參的原來的值;
2)非final修飾的類、陣列、介面作為方法中的引數,進行的引用傳遞(地址傳遞),對形參修改後實參也會改變,因為二者指向的是同一個例項;
3)final修飾的類作為方法中的引數,因為final的存在初始化後值不可變,每次操作都相當於產生一個新的例項物件,因此對形參修改時,實參也不受影響。
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("-------------------------------------------------------------"); //String、Char、Byte、Short、Integer、Long、Float、Double等final修飾的類 //對形參修改時實參不受影響 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的存在初始化後值不可變,每次操作都相當於產生一個新的例項物件,因此對形參修改時,實參也不受影響。