咱們先定義一個簡單的類:
class Vehicle {
int passengers;
int fuelcap;
int mpg;
}
有了這個模板,就能夠用它來建立對象: ---若對對象與類概念模糊的能夠看: 對象與類詳解
Vehicle veh1 = new Vehicle();
一般把這條語句的動做稱之爲建立一個對象,其實,它包含了四個動做。
1.右邊的new Vehicle();是以Vehicle類爲模板,在堆空間裏面建立一個Vehicle類對象(簡稱爲Vehicle對象) ---對java堆棧概念模糊的能夠看上一篇 JAVA中堆棧和內存分配 (這兒建議僅僅先看第一部分堆棧的介紹,下面看完以後再看堆棧其他的比較好)
2.末尾的()意味着,在對象建立成功以後,調用Vehicle類的構造函數,對剛生成的對象進行初始化。若沒有,則java會補上一個默認的構造函數。
3.左邊的Vehicle veh1 是在棧空間裏面建立了一個Vehicle類的引用變量,名字爲veh1,就是之後能夠來指向Vehicle對象的引用。
4。中間的 = 操做符讓 veh1引用指向剛建立的 Vehicle對象--此時veh1裏面存儲的是Vehicle對象的地址(至關於放在的門牌號)。
這個語句能夠拆分爲兩部來寫:Vehicle veh1; veh1 = new Vehicle(); 這樣看的比較明白,一個是對象引用變量,另外一個把對象地址賦值給引用。
一個Vehicle類能夠據此建立出無數個對象,這些對象不可能全叫「Vehicle」。
對象連名都沒有,無法直接訪問它。咱們只能經過對象引用來間接訪問對象。
爲了形象地說明對象、引用及它們之間的關係,能夠作一個或許不很穩當的比喻。對象比如是一隻很大的氣球,大到咱們抓不住它。引用變量是一根繩, 能夠用來系汽球。
若是隻執行了第一條語句(Vehicle veh1; ),還沒執行第二條(veh1 = new Vehicle();),此時建立的引用變量veh1還沒指向任何一個對象,它的值是null。引用變量能夠指向某個對象,或者爲null。
它是一根繩,一根尚未繫上任何一個汽球的繩。執行了第二句後,一隻新汽球作出來了,並被系在veh1這根繩上。咱們抓住這根繩,就等於抓住了那隻汽球。
再來一句:
Vehicle veh2;
就又作了一根繩,還沒繫上汽球。若是再加一句:
veh2 = veh1;
繫上了。這裏,發生了複製行爲。可是,要說明的是,對象自己並無被複制,被複制的只是對象地址。結果是,veh2也指向了veh1所指向的對象。兩根繩系的是同一只汽球。
若是用下句再建立一個對象:
veh2 = new Vehicle();
則引用變量veh2改指向第二個對象。此時veh1沒有改變,固然指向的仍是第一個對象。
從以上敘述再推演下去,咱們能夠得到如下結論:
(1)一個對象引用能夠指向0個或1個對象(一根繩子能夠不繫汽球,也能夠系一個汽球);
(2)一個對象能夠有N個引用指向它(能夠有N條繩子繫住一個汽球)。--注意:一個對象能夠沒有引用指向(氣球能夠白不用繩子繫住),通常用來輸出,例如:system.out.println(new Vehicle());
若是再來下面語句:
veh1 = veh2;
按上面的推斷,veh1也指向了第二個對象。這個沒問題。問題是第一個對象呢?沒有一條繩子繫住它,它飛了。多數書裏說,它被Java的垃圾回收機制回收了。
這不確切。正確地說,它已成爲垃圾回收機制的處理對象。至於何時真正被回收,那要看垃圾回收機制的心情了。
由此看來,下面的語句應該不合法吧?至少是沒用的吧?
new Vehicle();
不對。它是合法的,並且可用的。譬如,若是咱們僅僅爲了打印而生成一個對象,就不須要用引用變量來繫住它。最多見的就是打印字符串:System.out.println(「I am Java!」);
字符串對象「I am Java!」在打印後即被丟棄。有人把這種對象稱之爲臨時對象。
對象與引用的關係將持續到對象回收 上面的大部分摘錄於 JAVA 對象引用,以及對象賦值html
看到這兒應該大概的明白了吧?下面看代碼來驗證一下java
public class TestC { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "TestC [id=" + id + ", name=" + name + "]"; } public TestC(){}; public TestC(int id,String name){ this.id=id; this.name=name; }; public static void main(String[] args){ TestC t1=new TestC(1,"張三");//新建對象,把對象地址賦值給引用 TestC t2=t1;//把第一個引用(對象地址)賦值給第二個引用 System.out.println("t1:"+t1.toString()+"--t2:"+t2.toString()); t2.setId(2);//改變第二個引用的值 t2.setName("李四"); System.out.println("t1:"+t1.toString()+"--t2:"+t2.toString()); } }
咱們會看到結果:ide
t1:TestClone [id=1, name=張三]--t2:TestClone [id=1, name=張三]
t1:TestClone [id=2, name=李四]--t2:TestClone [id=2, name=李四]函數
咱們會看到這兩個引用結果是同樣的:由於第二個引用和第一個引用對應的是同一個對象。由於對象都是同一個,因此無論他怎麼變化,兩個引用的結果都是同樣的。(用上面的氣球理論就是系的同一個氣球,無論這個氣球是扁了仍是圓了,都是這一個)this
這時,可能會想到,要是引用不同的話是否是就會變化了?在後面追加代碼,咱們看一下:.net
System.out.println("=======開始不同了======="); t2=new TestC(3,"王五"); System.out.println("t1:"+t1.toString()+"--t2:"+t2.toString());
t1:TestC [id=1, name=張三]--t2:TestC [id=1, name=張三]
t1:TestC [id=2, name=李四]--t2:TestC [id=2, name=李四]
=======開始不同了=======
t1:TestC [id=2, name=李四]--t2:TestC [id=3, name=王五]code
這些這兩個引用就不一樣了。由於第二個引用指向了第二個新建的對象(第二個引用存儲了第二個對象的地址),而第一個引用沒有變化(上面的氣球舉例:第二根線如今綁第二個氣球了,而第一根線還在第一個氣球上。因此如今每根繩子都各自有一個氣球,彼此沒有相互的聯繫了)htm
好了,引用對對象的關係大概就是這樣了,建議能夠看下上面提到的 JAVA中堆棧和內存分配 瞭解更深層對象
不過若你想僅僅複製對象的值,不要對象的地址的話則能夠實現clone接口,或者序列化,見下篇介紹--對象的複製blog