偶然想起Java中對象和引用的基本概念,為了加深下對此的理解和認識,特地整理一下相關的知識點,通過具體實例從兩者的概念和區別兩方面去更形象的認識理解,再去記憶。
一、對象和引用的概念:
在Java中萬物皆對象,比如我們定義一個簡單的動物類:
class Animal {
String count;
String weight;
....
}
有了這個Animal類之后,我們可以來創建一個Animal對象:
Animal an = new Animal();
我們把編寫這個語句的動作就稱作創建一個對象,細化這個動作為:
1. 右面的”new Animal”,是以Animal類為模板的,在堆空間里創建一個Animal對象;
2. 末尾的”( )”代表着:在對象創建之后,立即調用Animal類的構造函數,對新生成的對象進行初始化。(如果沒構造函數,Java會有一個默認的構造函數的);
3. 左面的”Animal an” 創建了一個Animal類引用變量。即以后可以用來指向Animal對象的對象引用;
4. “=” 操作符使對象引用指向剛才創建的那個Animal對象。
拆分開也就是:等同於
Animal an;
an = new Animal();
有兩個實體:一個是對象引用變量;一個是對象本身。
在java中,都是通過引用來操縱對象的,這也是兩者的區別。
二、對象和引用的區別:
1、關聯性:
1). 當對象的引用變量指向對象時,兩者就互相聯系起來,改變引用的屬性,就會改變對象的屬性;
2). 如果同一個對象被多個引用變量引用的話,則這些引用變量將共同影響這個對象本身;
3). 在java中,都是通過引用來操縱對象的。
2、差異性:
1). 一個對象可以被不同的引用變量來操縱,同時一個引用變量也可以指向不同的對象,但是同一時刻下只能指向一個對象。
2). 從存儲空間上來看,對象和引用也是相互獨立的,對象一般存儲在堆中,而引用存儲在堆棧中(存儲速度而更快)。
對於引用變量的深層含義,未必在初學的時候就能深刻理解,
所以理解好下面這兩句話的真正含義非常重要
Case cc=new Case();
Case cc;
cc=new Case();
1.先搞清楚什么是堆,什么是棧。
Java開辟了兩類存儲區域,對比二者的特點
存儲區域 | 存儲內容 | 優點 | 缺點 | 回收 |
---|---|---|---|---|
棧 | 基本類型的變量和對象的引用變量 | 存取速度比堆要快,僅次於寄存器,棧數據可以共享 | 存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量 | 當超過變量的作用域后,Java會自動釋放掉該變量,內存空間可以立即被另作他用 |
堆 | 由new等指令創建的對象和數組 | 可以動態地分配內存大小,生存期也不必事先告訴編譯器 | 由於要在運行時動態分配內存,存取速度較慢 | 由Java虛擬機的自動垃圾回收器來回收不再使用的數據 |
堆棧的存儲特點決定了其中存儲的數據類型。
注意,棧內存儲的除了基本類型的變量(String, int 這種類型的變量)還會存儲對象的引用變量。java中,引用變量實際上是一個指針,它指向的是堆內存中對象實例。
引用變量就相當於是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。
2.給引用變量賦值
回過頭再來看代碼
實際上里面分解成了四個步驟。
Case cc; '''在棧內存里面開辟了空間給引用變量cc,這時cc=null'''
cc=new Case();
''' 1. new Case()在堆內存里面開辟了空間給Case類的對象,這個對象沒有名字 2. Case()隨即調用了Case類的構造函數 3. 把對象的地址在堆內存的地址給引用變量cc '''
這樣我們就明確了:
- Java中,這里的“=”並不是賦值的意思,而是把對象的地址傳遞給變量;
- 對象創建出來,其實連名字都沒有,因此必須通過引用變量來對其進行操作。
為了形象地說明對象、引用及它們之間的關系,可以做一個或許不很妥當的比喻。對象好比是一只很大的氣球,大到我們抓不住它。引用變量是一根繩, 可以用來系汽球
緊接着就會問,引用變量是怎么傳遞的呢?
這就涉及到Java唯一的參數傳遞方式——按值傳遞
看下面一段代碼:
public class ObjectRef {
'''基本類型的參數傳遞'''
public static void testBasicType(int m) {
System.out.println("m=" + m);//m=50
m = 100;
System.out.println("m=" + m);//m=100
}
'''參數為對象,不改變引用的值'''
'''s即sMain指向的對象執行了append方法,在原來的字符串上加了段“_add”'''
public static void add(StringBuffer s) {
s.append("_add");
}
'''參數為對象,改變引用的值 '''
'''引用變量指向了一個新的對象,已經不是sMain指向的對象了'''
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
}
public static void main(String[] args) {
int i = 50;
testBasicType(i);
System.out.println(i);'''i=50'''
StringBuffer sMain = new StringBuffer("init");
System.out.println("sMain=" + sMain.toString());'''sMain=init'''
add(sMain);
System.out.println("sMain=" + sMain.toString());'''sMain=init_add'''
changeRef(sMain);
System.out.println("sMain=" + sMain.toString());'''sMain=Java'''
}
}
看這里,給人的感覺是傳遞過來的明明是對象的引用,為什么就是值得傳遞呢?
因為傳遞之前,被傳的就是個引用啊,我們所謂的“傳地址”,在傳之前,那可是一個實例,傳過來的是實例的地址。這里傳遞的值,從始至終就是個地址,sMain就是個地址,傳給s還是個地址。你們感受下:
'''參數為對象,不改變引用的值'''
'''s即sMain,指向的對象執行了append方法,在原來的字符串上加了段“_add”'''
public static void add(StringBuffer s) {
s.append("_add");
}
以上輸出的結果會是“init_add”
而這里,s引用了一個新的對象,根本沒有進行參數的傳遞,它和之前的sMain沒有關系了。
'''參數為對象,改變引用的值 '''
'''引用變量指向了一個新的對象,已經不是sMain指向的對象了'''
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
}
以上輸出的結果會是“Java”
引用《Java編程思想》中的一段話:
倘若“將一個對象賦值給另一個對象”,實際是將“引用”從一個地方復制到另一個地方.