深刻理解Java引用類型程序員
在Java中類型可分爲兩大類:值類型與引用類型。值類型就是基本數據類型(如int ,double 等),而引用類型,是指除了基本的變量類型以外的全部類型(如經過 class 定義的類型)。全部的類型在內存中都會分配必定的存儲空間(形參在使用的時候也會分配存儲空間,方法調用完成以後,這塊存儲空間自動消失), 基本的變量類型只有一塊存儲空間(分配在stack中), 而引用類型有兩塊存儲空間(一塊在stack中,一塊在heap中),在函數調用時Java是傳值仍是傳引用,這個估計不少人至今都很糊塗,下面用圖形與代碼來解釋:數組
在上圖中引用類型在傳參時不是在heap中再分配一塊內存來存變量c 所指向的A(),而是讓a 指向同一個A 的實例,這就與C++ 中的指針同樣,先聲明指針變量a,b,c,d 在傳參的時候讓a 指向c所指向的內存,讓 d 指向 b 所指向的內存。很明顯Java中的引用與C++中的指針在原理上是相相似的,但記住Java沒有指針,只有引用。下面再經過一些具體的代碼來討論引用:app
1. 簡單類型是按值傳遞的函數
Java 方法的參數是簡單類型的時候,是按值傳遞的 (pass by value)。這一點咱們能夠經過一個簡單的例子來講明:學習
package test;spa
public class Test {指針
//交換兩個變量的值對象
public static void Swap(int a,int b){內存
int c=a;io
a=b;
b=c;
System.out.println("a: "+a);
System.out.println("b: "+b);
}
public static void main(String[] args){
int c=10;
int d=20;
Swap(c,d);
System.out.println("After Swap:");
System.out.println("c: "+d);
System.out.println("d: "+c);
}
}
運行結果:
a: 20
b: 10
After Swap:
c: 20
d: 10
不難看出,雖然在 Swap (a,b) 方法中改變了傳進來的參數的值,但對這個參數源變量自己並無影響,即對 main(String[]) 方法裏的 a,b 變量沒有影響。那說明,參數類型是簡單類型的時候,是按值傳遞的。以參數形式傳遞簡單類型的變量時,其實是將參數的值做了一個拷貝傳進方法函數的,那麼在方法函數裏再怎麼改變其值,其結果都是隻改變了拷貝的值,而不是源值。
2. 什麼是引用
Java 是傳值仍是傳引用,問題主要出在對象的傳遞上,由於 Java 中簡單類型沒有引用。既然爭論中提到了引用這個東西,爲了搞清楚這個問題,咱們必需要知道引用是什麼。
簡單的說,引用其實就像是一個對象的名字或者別名 (alias),一個對象在內存中會請求一塊空間來保存數據,根據對象的大小,它可能須要佔用的空間大小也不等。訪問對象的時候,咱們不會直接是訪問對象在內存中的數據,而是經過引用去訪問。引用也是一種數據類型,咱們能夠把它想象爲相似 C++ 語言中指針的東西,它指示了對象在內存中的地址——只不過咱們不可以觀察到這個地址到底是什麼。
若是咱們定義了不止一個引用指向同一個對象,那麼這些引用是不相同的,由於引用也是一種數據類型,須要必定的內存空間(stack,棧空間)來保存。可是它們的值是相同的,都指示同一個對象在內存(heap,堆空間)的中位置。好比:
String a="This is a Text!";
String b=a;
經過上面的代碼和圖形示例不難看出,a 和 b 是不一樣的兩個引用,咱們使用了兩個定義語句來定義它們。但它們的值是同樣的,都指向同一個對象 "This is a Text!"。但要注意String 對象的值自己是不可更改的 (像 b = "World"; b = a; 這種狀況不是改變了 "World" 這一對象的值,而是改變了它的引用 b 的值使之指向了另外一個 String 對象 a)。
如圖,開始b 的值爲綠線所指向的「Word Two」,而後 b=a; 使 b 指向了紅線所指向的」Word「.
這裏我描述了兩個要點:
(1) 引用是一種數據類型(保存在stack中),保存了對象在內存(heap,堆空間)中的地址,這種類型即不是咱們平時所說的簡單數據類型也不是類實例(對象);
(2) 不一樣的引用可能指向同一個對象,換句話說,一個對象能夠有多個引用,即該類類型的變量。
3. 對象是如何傳遞的呢
隨着學習的深刻,你也許會對對象的傳遞方式產生疑問,即對象到底是「按值傳遞」仍是「按引用傳遞」?
(1)認爲是「按值傳遞」的:
package test;
public class Test {
public static void Sample(int a){
a+=20;
System.out.println("a: "+a);
}
public static void main(String[] args){
int b=10;
Sample(b);
System.out.println("b: "+b);
}
}
運行結果:
a: 30
b: 10
在這段代碼裏,修改變量 a 的值,不改變變量 b 的值,因此它是「值傳遞」。
(2)認爲是「按引用傳遞」的:
package test;
public class Test {
public static void Sample(StringBuffer a){
a.append(" Changed ");
System.out.println("a: "+a);
}
public static void main(String[] args){
StringBuffer b=new StringBuffer("This is a test!");
Sample(b);
System.out.println("b: "+b);
}
}
運行結果:
a: This is a test! Changed
b: This is a test! Changed
在Sample(StringBuffer)這個函數中,修改了引用 a 的值,同時 b 的值也變化了,因此它是「按引用傳遞」的!
那麼對象(記住在Java中一切皆對象,不管是int a;仍是String a;,這兩個變量a都是對象)在傳遞的時候到底是按什麼方式傳遞的呢?其答案就只能是:便是按值傳遞也是按引用傳遞,但一般基本數據類型(如int,double等)咱們認爲其是「值傳遞」,而自定義數據類型(class)咱們認爲其是「引用傳遞」。
4. 正確看待傳值仍是傳引用的問題
要正確的看待這個問題必需要搞清楚爲何會有這樣一個問題。
實際上,問題來源於 C,而不是 Java。
C 語言中有一種數據類型叫作指針,因而將一個數據做爲參數傳遞給某個函數的時候,就有兩種方式:傳值,或是傳指針。 在值傳遞時,修改函數中的變量值不會改變原有變量的值,可是經過指針卻會改變。
void Swap(int a,int b){ int c=a;a=b;b=c;}
void Swap(int *a,int *b){ int c=*a;*a=*b;*b=c; }
int c=10;
int d=20;
Swap(c,d); //不改變 c , d 的值
Swap(&c,&d); //改變 c , d 的值
許多的 C 程序員開始轉向學習 Java,他們發現,使用相似 SwapValue(T,T)(當T 爲值類型時) 的方法仍然不能改變經過參數傳遞進來的簡單數據類型的值,可是若是T時一個引用類型時,則可能將其成員隨意更改。因而他們以爲這很像是 C 語言中傳值/傳指針的問題。可是 Java 中沒有指針,那麼這個問題就演變成了傳值/傳引用的問題。惋惜將這個問題放在 Java 中進行討論並不恰當。
討論這樣一個問題的最終目的只是爲了搞清楚何種狀況才能在方法函數中方便的更改參數的值並使之長期有效。
5. 如何實現相似 swap 的方法
傳值仍是傳引用的問題,到此已經算是解決了,可是咱們仍然不能解決這樣一個問題:若是我有兩個 int型的變量 a 和 b,我想寫一個方法來交換它們的值,應該怎麼辦?有不少方法,這裏介紹一種簡單的方法:
package test;
public class Test {
public static void Swap(int[] a){
int c=a[0];
a[0]=a[1];
a[1]=c;
}
public static void main(String[] args){
int[] a=new int[2];
a[0]=10;
a[1]=20;
Swap(a);
System.out.println(a[0]);
System.out.println(a[1]);
}
}
經過數組能夠方便的實現值類型的數據源的交換,不過還有一種方法是將全部變量封裝到一個類裏面去,經過引用類型來實現。