學習Java的同學注意了!!!
學習進程中遇到甚么問題或想獲得學習資源的話,歡迎加入Java學習交換群,群號碼:183993990 我們1起學Java!
關鍵字: java對象 援用
Java對象及其援用
關于對象與援用之間的1些基本概念。
初學Java時,在很長1段時間里,總覺得基本概念很模糊。后來才知道,在許多Java書中,把對象和對象的援用混為1談。可是,如果我分不清對象與對象援用,
那實在沒法很好地理解下面的面向對象技術。把自己的1點認識寫下來,也許能讓初學Java的朋友們少走1點彎路。
為便于說明,我們先定義1個簡單的類:
class Vehicle {
int passengers;
int fuelcap;
int mpg;
}
有了這個模板,就能夠用它來創(chuàng)建對象:
Vehicle veh1 = new Vehicle();
通常把這條語句的動作稱之為創(chuàng)建1個對象,其實,它包括了4個動作。
1)右側的“new Vehicle”,是以Vehicle類為模板,在堆空間里創(chuàng)建1個Vehicle類對象(也簡稱為Vehicle對象)。
2)末尾的()意味著,在對象創(chuàng)建后,立即調用Vehicle類的構造函數,對剛生成的對象進行初始化。構造函數是肯定有的。如果你沒寫,Java會給你補上1個默許的構造函數。
3)左側的“Vehicle veh 1”創(chuàng)建了1個Vehicle類援用變量。所謂Vehicle類援用,就是以后可以用來指向Vehicle對象的對象援用。
4)“=”操作符使對象援用指向剛創(chuàng)建的那個Vehicle對象。
我們可以把這條語句拆成兩部份:
Vehicle veh1;
veh1 = new Vehicle();
效果是1樣的。這樣寫,就比較清楚了,有兩個實體:1是對象援用變量,1是對象本身。
在堆空間里創(chuàng)建的實體,與在數據段和棧空間里創(chuàng)建的實體不同。雖然它們也是確確切實存在的實體,但是,我們看不見,也摸不著。不但如此,
我們仔細研究1下第2句,找找剛創(chuàng)建的對象叫甚么名字?有人說,它叫“Vehicle”。不對,“Vehicle”是類(對象的創(chuàng)建模板)的名字。
1個Vehicle類可以據此創(chuàng)建出無數個對象,這些對象不可能全叫“Vehicle”。
對象連名都沒有,沒法直接訪問它。我們只能通過對象援用來間接訪問對象。
為了形象地說明對象、援用及它們之間的關系,可以做1個也許不很妥當的比喻。對象好比是1只很大的氣球,大到我們抓不住它。援用變量是1根繩, 可以用來系汽球。
如果只履行了第1條語句,還沒履行第2條,此時創(chuàng)建的援用變量veh1還沒指向任何1個對象,它的值是null。援用變量可以指向某個對象,或為null。
它是1根繩,1根還沒有系上任何1個汽球的繩。履行了第2句后,1只新汽球做出來了,并被系在veh1這根繩上。我們捉住這根繩,就等于捉住了那只汽球。
再來1句:
Vehicle veh2;
就又做了1根繩,還沒系上汽球。如果再加1句:
veh2 = veh1;
系上了。這里,產生了復制行動。但是,要說明的是,對象本身并沒有被復制,被復制的只是對象援用。結果是,veh2也指向了veh1所指向的對象。兩根繩系的是同1只汽球。
如果用下句再創(chuàng)建1個對象:
veh2 = new Vehicle();
則援用變量veh2改指向第2個對象。
從以上敘述再推演下去,我們可以取得以下結論:
(1)1個對象援用可以指向0個或1個對象(1根繩子可以不系汽球,也能夠系1個汽球);
(2)1個對象可以有N個援用指向它(可以有N條繩子系住1個汽球)。
如果再來下面語句:
veh1 = veh2;
按上面的推斷,veh1也指向了第2個對象。這個沒問題。問題是第1個對象呢?沒有1條繩子系住它,它飛了。多數書里說,它被Java的垃圾回收機制回收了。
這不確切。正確地說,它已成為垃圾回收機制的處理對象。至于甚么時候真正被回收,那要看垃圾回收機制的心情了。
由此看來,下面的語句應當不合法吧?最少是沒用的吧?
new Vehicle();
不對。它是合法的,而且可用的。比方,如果我們僅僅為了打印而生成1個對象,就不需要用援用變量來系住它。最多見的就是打印字符串:
System.out.println(“I am Java!”);
字符串對象“I am Java!”在打印后即被拋棄。有人把這類對象稱之為臨時對象。
對象與援用的關系將延續(xù)到對象回收。
Java對象及援用
Java對象及援用是容易混淆卻又必須掌握的基礎知識,本章論述Java對象和援用的概念,和與其密切相干的參數傳遞。
先看下面的程序:
StringBuffer s;
s = new StringBuffer("Hello World!");
第1個語句僅為援用(reference)分配了空間,而第2個語句則通過調用類(StringBuffer)的構造函數StringBuffer(String str)為類生成了1個實例(或稱為對象)。這兩個操作被完成后,對象的內容則可通過s進行訪問——在Java里都是通過援用來操縱對象的。
Java對象和援用的關系可以說是相互關聯,卻又彼此獨立。彼此獨立主要表現在:援用是可以改變的,它可以指向別的對象,比方上面的s,你可以給它另外的對象,如:
s = new StringBuffer("Java");
這樣1來,s就和它指向的第1個對象脫離關系。
從存儲空間上來講,對象和援用也是獨立的,它們存儲在不同的地方,對象1般存儲在堆中,而援用存儲在速度更快的堆棧中。
援用可以指向不同的對象,對象也能夠被多個援用操縱,如:
StringBuffer s1 = s;
這條語句使得s1和s指向同1個對象。既然兩個援用指向同1個對象,那末不管使用哪一個援用操縱對象,對象的內容都產生改變,并且只有1份,通過s1和s得到的內容自然也1樣,(String除外,由于String始終不變,String s1=”AAAA”; String s=s1,操作s,s1由于始終不變,所以為s另外開辟了空間來存儲s,)以下面的程序:
StringBuffer s;
s = new StringBuffer("Java");
StringBuffer s1 = s;
s1.append(" World");
System.out.println("s1=" + s1.toString());//打印結果為:s1=Java World
System.out.println("s=" + s.toString());//打印結果為:s=Java World
上面的程序表明,s1和s打印出來的內容是1樣的,這樣的結果看起來讓人非常疑惑,但是仔細想一想,s1和s只是兩個援用,它們只是操縱桿而已,它們指向同1個對象,操縱的也是同1個對象,通過它們得到的是同1個對象的內容。這就像汽車的剎車和油門,它們操縱的都是車速,假設汽車開始的速度是80,然后你踩了1次油門,汽車加速了,假設車速升到了120,然后你踩1下剎車,此時車速是從120開始降落的,假設降落到60,再踩1次油門,車速則從60開始上升,而不是從第1次踩油門后的120開始。也就是說車速同時受油門和剎車影響,它們的影響是積累起來的,而不是各自獨立(除非剎車和油門不在1輛車上)。所以,在上面的程序中,不管使用s1還是s操縱對象,它們對對象的影響也是積累起來的(更多的援用同理)。
只有理解了對象和援用的關系,才能理解參數傳遞。
1般面試題中都會考Java傳參的問題,并且它的標準答案是Java只有1種參數傳遞方式:那就是按值傳遞,即Java中傳遞任何東西都是傳值。如果傳入方法的是基本類型的東西,你就得到此基本類型的1份拷貝。如果是傳遞援用,就得到援用的拷貝。
1般來講,對基本類型的傳遞,我們很容易理解,而對對象,總讓人感覺是按援用傳遞,看下面的程序:
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
}
//參數為對象,不改變援用的值 ??????
public static void add(StringBuffer s) {
s.append("_add");
}
//參數為對象,改變援用的值 ?????
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=init_add
}
}
以上程序的允許結果顯示出,testBasicType方法的參數是基本類型,雖然參數m的值產生改變,但其實不影響i。
add方法的參數是1個對象,當把sMain傳給參數s時,s得到的是sMain的拷貝,所以s和sMain指向同1個對象,因此,使用s操作影響的其實就是sMain指向的對象,故調用add方法后,sMain指向的對象的內容產生了改變。
在changeRef方法中,參數也是對象,當把sMain傳給參數s時,s得到的是sMain的拷貝,但與add方法不同的是,在方法體內改變了s指向的對象(也就是s指向了別的對象,牽著氣球的繩子換氣球了),給s重新賦值后,s與sMain已毫無關聯,它和sMain指向了不同的對象,所以不管對s做甚么操作,都不會影響sMain指向的對象,故調用changeRef方法前后sMain指向的對象內容并未產生改變。
對add方法的調用結果,可能很多人會有這類感覺:這不明明是按援用傳遞嗎?對這類問題,還是套用Bruce Eckel的話:這依賴于你如何看待援用,終究你會明白,這個爭辯并沒那末重要。真正重要的是,你要理解,傳援用使得(調用者的)對象的修改變得不可預期。
public class Test
{ public int i,j;
public void test_m(Test a)
{ Test b = new Test();
b.i = 1;
b.j = 2;
a = b;
}
public void test_m1(Test a )
{ a.i = 1;
a.j = 2;
}
public static void main(String argv[])
{ Test t= new Test();
t.i = 5;
t.j = 6;
System.out.println( "t.i = "+ t.i + " t.j= " + t.j); //5,6
t.test_m(t);
System.out.println( "t.i = "+ t.i + " t.j= " + t.j); //5,6,a和t都指向了1個對象,而在test_m中s又指向了另外一個對象,所以對象t不變!!!
t.test_m1(t);
System.out.println( "t.i = "+ t.i + " t.j= " + t.j); //1,2
}
}
答案只有1個:Java里都是按值傳遞參數。而實際上,我們要明白,當參數是對象時,傳援用會產生甚么狀態(tài)(就像上面的add方法)?
=========================================================================
樓主,這樣來記這個問題
以下表達式:
A a1 = new A();
它代表A是類,a1是援用,a1不是對象,new A()才是對象,a1援用指向new A()這個對象。
在JAVA里,“=”不能被看成是1個賦值語句,它不是在把1個對象賦給另外1個對象,它的履行進程實質上是將右側對象的地址傳給了左側的援用,使得左側的援用指向了右側的對象。JAVA表面上看起來沒有指針,但它的援用其實質就是1個指針,援用里面寄存的其實不是對象,而是該對象的地址,使得該援用指向了對象。在JAVA里,“=”語句不應當被翻譯成賦值語句,由于它所履行的確切不是1個賦值的進程,而是1個傳地址的進程,被譯成賦值語句會造成很多誤解,譯得不準確。
再如:
A a2;
它代表A是類,a2是援用,a2不是對象,a2所指向的對象為空null;
再如:
a2 = a1;
它代表,a2是援用,a1也是援用,a1所指向的對象的地址傳給了a2(傳址),使得a2和a1指向了同1對象。
綜上所述,可以簡單的記為,在初始化時,“=”語句左側的是援用,右側new出來的是對象。
在后面的左右都是援用的“=”語句時,左右的援用同時指向了右側援用所指向的對象。
再所謂實例,其實就是對象的同義詞。
如果需要賦值,就需要類實現Cloneable接口,實現clone()方法。
1 2 3 4 5 6 7 8 9 10 11 |
class D implements Cloneable{//實現Cloneable接口 String sex; D(String sex){ this.sex=sex; } @Override protected Object clone() throws CloneNotSupportedException { // 實現clone方法 return super.clone(); } } |
賦值的時候:
1 2 |
D d=new D("男"); D d2=(D) d.clone();//把d賦值給d2 |
如果類中的變量不是主類型,而是對象,也需要調用該對象的clone()方法
下面是1個完全的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class Test2 { public static void main(String[] args) throws CloneNotSupportedException { // TODO Auto-generated method stub D d=new D("男"); C c=new C("張3","20",d); C new_c=(C) c.clone();//調用clone方法來賦值 new_c.name="李4"; d.sex="女";//d System.out.println(c.d.sex); System.out.println(c.name); } } class C implements Cloneable{ String name; String age; D d; C(String name,String age,D d) throws CloneNotSupportedException{ this.name=name; this.age=age; this.d=(D) d.clone();//調用clone方法來賦值,這樣即使外部的d產生變化,c里的也不會變 } @Override protected Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub return super.clone(); } } class D implements Cloneable{//實現Cloneable接口 String sex; D(String sex){ this.sex=sex; } @Override protected Object clone() throws CloneNotSupportedException { // 實現clone方法 return super.clone(); } } |
學習Java的同學注意了!!!
學習進程中遇到甚么問題或想獲得學習資源的話,歡迎加入Java學習交換群,群號碼:183993990 我們1起學Java!
上一篇 Java基礎之This用法
下一篇 移動端游戲架構設計