1. 程式人生 > >java引用傳遞還是值傳遞?

java引用傳遞還是值傳遞?

app 分享 jvm內存模型 作用 相同 http thumb 第一個 bject

首先,不要糾結於 Pass By Value 和 Pass By Reference 的字面上的意義,否則很容易陷入所謂的“一切傳引用其實本質上是傳值”這種並不能解決問題無意義論戰中。
更何況,要想知道Java到底是傳值還是傳引用,起碼你要先知道傳值和傳引用的準確含義吧?可是如果你已經知道了這兩個名字的準確含義,那麽你自己就能判斷Java到底是傳值還是傳引用。
這就好像用大學的名詞來解釋高中的題目,對於初學者根本沒有任何意義。

一:搞清楚 基本類型 和 引用類型的不同之處

int num = 10;
String str = "hello";

  技術分享圖片

如圖所示,num是基本類型,值就直接保存在變量中。而str是引用類型,變量中保存的只是實際對象的地址。一般稱這種變量為"引用",引用指向實際對象,實際對象中保存著內容。

二:搞清楚賦值運算符(=)的作用

num = 20;
str = "java";

  技術分享圖片

對於基本類型 num ,賦值運算符會直接改變變量的值,原來的值被覆蓋掉。
對於引用類型 str,賦值運算符會改變引用中所保存的地址,原來的地址被覆蓋掉。但是原來的對象不會被改變(重要)。
如上圖所示,"hello" 字符串對象沒有被改變。(沒有被任何引用所指向的對象是垃圾,會被垃圾回收器回收)

三:調用方法時發生了什麽?參數傳遞基本上就是賦值操作

第一個例子:基本類型
void foo(int value) {
    value = 100;
}
foo(num); // num 沒有被改變

第二個例子:沒有提供改變自身方法的引用類型
void foo(String text) {
    text = "windows";
}
foo(str); // str 也沒有被改變

第三個例子:提供了改變自身方法的引用類型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder.append("4");
}
foo(sb); // sb 被改變了,變成了"iphone4"。

第四個例子:提供了改變自身方法的引用類型,但是不使用,而是使用賦值運算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}
foo(sb); // sb 沒有被改變,還是 "iphone"。

  

重點理解為什麽,第三個例子和第四個例子結果不同?

下面是第三個例子的圖解:

技術分享圖片

builder.append("4")之後

  技術分享圖片

下面是第四個例子的圖解:

技術分享圖片

builder = new StringBuilder("ipad"); 之後

  技術分享圖片


從局部變量/方法參數開始講起,各種類型數據在內存中的存儲方式:

局部變量和方法參數在jvm中的儲存方法是相同的,都是在棧上開辟空間來儲存的,隨著進入方法開辟,退出方法回收。以32位JVM為例,boolean/byte/short/char/int/float以及引用都是分配4字節空間,long/double分配8字節空間。對於每個方法來說,最多占用多少空間是一定的,這在編譯時就可以計算好。

我們都知道JVM內存模型中有,stack和heap的存在,但是更準確的說,是每個線程都分配一個獨享的stack,所有線程共享一個heap。對於每個方法的局部變量來說,是絕對無法被其他方法,甚至其他線程的同一方法所訪問到的,更遑論修改。

當我們在方法中聲明一個 int i = 0,或者 Object obj = null 時,僅僅涉及stack,不影響到heap,當我們 new Object() 時,會在heap中開辟一段內存並初始化Object對象。當我們將這個對象賦予obj變量時,僅僅是stack中代表obj的那4個字節變更為這個對象的地址。

數組類型引用和對象:

當我們聲明一個數組時,如int[] arr = new int[10],因為數組也是對象,arr實際上是引用,stack上僅僅占用4字節空間,new int[10]會在heap中開辟一個數組對象,然後arr指向它。

當我們聲明一個二維數組時,如 int[][] arr2 = new int[2][4],arr2同樣僅在stack中占用4個字節,會在內存中開辟一個長度為2的,類型為int[]的數組,然後arr2指向這個數組。這個數組內部有兩個引用(大小為4字節),分別指向兩個長度為4的類型為int的數組。

技術分享圖片


所以當我們傳遞一個數組引用給一個方法時,數組的元素是可以被改變的,但是無法讓數組引用指向新的數組。

你還可以這樣聲明:int[][] arr3 = new int[3][],這時內存情況如下圖

技術分享圖片

你還可以這樣 arr3[0] = new int [5]; arr3[1] = arr2[0];

技術分享圖片

關於String:

原本回答中關於String的圖解是簡化過的,實際上String對象內部僅需要維護三個變量,char[] chars, int startIndex, int length。而chars在某些情況下是可以共用的。但是因為String被設計成為了不可變類型,所以你思考時把String對象簡化考慮也是可以的。

String str = new String("hello")

技術分享圖片

當然某些JVM實現會把"hello"字面量生成的String對象放到常量池中,而常量池中的對象可以實際分配在heap中,有些實現也許會分配在方法區,當然這對我們理解影響不大。



java引用傳遞還是值傳遞?