Java多態(tài)
1善涨、多態(tài)的總結(jié)
面向?qū)ο缶幊逃腥筇匦裕悍庋b窒盐、繼承、多態(tài)钢拧。
封裝隱藏了類的內(nèi)部實現(xiàn)機制蟹漓,可以在不影響使用的情況下改變類的內(nèi)部結(jié)構(gòu),同時也保護(hù)了數(shù)據(jù)源内。對外界而已它的內(nèi)部細(xì)節(jié)是隱藏的葡粒,暴露給外界的只是它的訪問方法。
繼承是為了重用父類代碼膜钓。兩個類若存在IS-A的關(guān)系就可以使用繼承嗽交。,同時繼承也為實現(xiàn)多態(tài)做了鋪墊颂斜。那么什么是多態(tài)呢夫壁?多態(tài)的實現(xiàn)機制又是什么?請看我一一為你揭開:
所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定焚鲜,而是在程序運行期間才確定掌唾,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法忿磅,必須在由程序運行期間才能決定糯彬。因為在程序運行時才確定具體的類,這樣葱她,不用修改源程序代碼撩扒,就可以讓引用變量綁定到各種不同的類實現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼搓谆,讓程序可以選擇多個運行狀態(tài)炒辉,這就是多態(tài)性。
比如你是一個酒神泉手,對酒情有獨鐘黔寇。某日回家發(fā)現(xiàn)桌上有幾個杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒斩萌,只有喝了之后才能夠猜出來是何種酒缝裤。你一喝,這是劍南春颊郎、再喝這是五糧液憋飞、再喝這是酒鬼酒….在這里我們可以描述成如下:
酒 a = 劍南春
酒 b = 五糧液
酒 c = 酒鬼酒
這里所表現(xiàn)的的就是多態(tài)。劍南春姆吭、五糧液榛做、酒鬼酒都是酒的子類,我們只是通過酒這一個父類就能夠引用不同的子類内狸,這就是多態(tài)——我們只有在運行的時候才會知道引用變量所指向的具體實例對象检眯。
誠然,要理解多態(tài)我們就必須要明白什么是“向上轉(zhuǎn)型”答倡。在繼承中我們簡單介紹了向上轉(zhuǎn)型轰传,這里就在啰嗦下:在上面的喝酒例子中驴党,酒(Win)是父類瘪撇,劍南春(JNC)、五糧液(WLY)港庄、酒鬼酒(JGJ)是子類倔既。我們定義如下代碼:
JNC a = new JNC();
對于這個代碼我們非常容易理解無非就是實例化了一個劍南春的對象嘛!但是這樣呢鹏氧?
Wine a = new JNC();
在這里我們這樣理解渤涌,這里定義了一個Wine 類型的a,它指向JNC對象實例把还。由于JNC是繼承與Wine实蓬,所以JNC可以自動向上轉(zhuǎn)型為Wine,所以a是可以指向JNC實例對象的吊履。這樣做存在一個非常大的好處安皱,在繼承中我們知道子類是父類的擴展,它可以提供比父類更加強大的功能艇炎,如果我們定義了一個指向子類的父類引用類型酌伊,那么它除了能夠引用父類的共性外,還可以使用子類強大的功能缀踪。
但是向上轉(zhuǎn)型存在一些缺憾居砖,那就是它必定會導(dǎo)致一些方法和屬性的丟失虹脯,而導(dǎo)致我們不能夠獲取它們。所以父類類型的引用可以調(diào)用父類中定義的所有屬性和方法奏候,對于只存在與子類中的方法和屬性它就望塵莫及了
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}
public void fun2(){
System.out.println("Wine 的Fun2...");
}
}
public class JNC extends Wine{
/**
* @desc 子類重載父類方法
* 父類中不存在該方法循集,向上轉(zhuǎn)型后,父類是不能引用該方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}
/**
* 子類重寫父類方法
* 指向子類的父類引用調(diào)用fun2時蔗草,必定是調(diào)用該方法
*/
public void fun2(){
System.out.println("JNC 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Wine a = new JNC();
a.fun1();
}
}
Output:
Wine 的Fun.....
JNC 的Fun2...
從程序的運行結(jié)果中我們發(fā)現(xiàn)暇榴,a.fun1()首先是運行父類Wine中的fun1().然后再運行子類JNC中的fun2()。
分析:在這個程序中子類JNC重載了父類Wine的方法fun1()蕉世,重寫fun2()蔼紧,而且重載后的fun1(String a)與 fun1()不是同一個方法,由于父類中沒有該方法狠轻,向上轉(zhuǎn)型后會丟失該方法奸例,所以執(zhí)行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() 向楼,那么指向JNC的Wine引用會調(diào)用JNC中fun2()方法查吊。
所以對于多態(tài)我們可以總結(jié)如下:
指向子類的父類引用由于向上轉(zhuǎn)型了,它只能訪問父類中擁有的方法和屬性湖蜕,而對于子類中存在而父類中不存在的方法逻卖,該引用是不能使用的,盡管是重載該方法昭抒。若子類重寫了父類中的某些方法评也,在調(diào)用該些方法的時候,必定是使用子類中定義的這些方法(動態(tài)連接灭返、動態(tài)調(diào)用)盗迟。
對于面向?qū)ο蠖眩鄳B(tài)分為編譯時多態(tài)和運行時多態(tài)熙含。其中編輯時多態(tài)是靜態(tài)的罚缕,主要是指方法的重載,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的函數(shù)怎静,通過編輯之后會變成兩個不同的函數(shù)邮弹,在運行時談不上多態(tài)腌乡。而運行時多態(tài)是動態(tài)的,它是通過動態(tài)綁定來實現(xiàn)的,也就是我們所說的多態(tài)性型檀。
2、多態(tài)的實現(xiàn)
2.1實現(xiàn)條件
在剛剛開始就提到了繼承在為多態(tài)的實現(xiàn)做了準(zhǔn)備。子類Child繼承父類Father无埃,我們可以編寫一個指向子類的父類類型引用灵疮,該引用既可以處理父類Father對象荔棉,也可以處理子類Child對象诉植,當(dāng)相同的消息發(fā)送給子類或者父類對象時,該對象就會根據(jù)自己所屬的引用而執(zhí)行不同的行為,這就是多態(tài)。即多態(tài)性就是相同的消息使得不同的類做出不同的響應(yīng)席怪。
Java實現(xiàn)多態(tài)有三個必要條件:繼承船万、重寫、向上轉(zhuǎn)型捧搞。
繼承:在多態(tài)中必須存在有繼承關(guān)系的子類和父類晚树。
重寫:子類對父類中某些方法進(jìn)行重新定義婚瓜,在調(diào)用這些方法時就會調(diào)用子類的方法。
向上轉(zhuǎn)型:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法妈经。
只有滿足了上述三個條件鳄厌,我們才能夠在同一個繼承結(jié)構(gòu)中使用統(tǒng)一的邏輯實現(xiàn)代碼處理不同的對象,從而達(dá)到執(zhí)行不同的行為。
對于Java而言伶氢,它多態(tài)的實現(xiàn)機制遵循一個原則:當(dāng)超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法瘪吏,但是這個被調(diào)用的方法必須是在超類中定義過的癣防,也就是說被子類覆蓋的方法。
2.2實現(xiàn)形式
在Java中有兩種形式可以實現(xiàn)多態(tài)掌眠。繼承和接口蕾盯。
2.2.1、基于繼承實現(xiàn)的多態(tài)
基于繼承的實現(xiàn)機制主要表現(xiàn)在父類和繼承該父類的一個或多個子類對某些方法的重寫蓝丙,多個子類對同一方法的重寫可以表現(xiàn)出不同的行為级遭。
public class Wine {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wine(){
}
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return null;
}
}
public class JNC extends Wine{
public JNC(){
setName("JNC");
}
/**
* 重寫父類方法,實現(xiàn)多態(tài)
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
public class JGJ extends Wine{
public JGJ(){
setName("JGJ");
}
/**
* 重寫父類方法渺尘,實現(xiàn)多態(tài)
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
public class Test {
public static void main(String[] args) {
//定義父類數(shù)組
Wine[] wines = new Wine[2];
//定義兩個子類
JNC jnc = new JNC();
JGJ jgj = new JGJ();
//父類引用子類對象
wines[0] = jnc;
wines[1] = jgj;
for(int i = 0 ; i < 2 ; i++){
System.out.println(wines[i].toString() + "--" + wines[i].drink());
}
System.out.println("-------------------------------");
}
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
在上面的代碼中JNC挫鸽、JGJ繼承Wine,并且重寫了drink()鸥跟、toString()方法丢郊,程序運行結(jié)果是調(diào)用子類中方法,輸出JNC锌雀、JGJ的名稱蚂夕,這就是多態(tài)的表現(xiàn)。不同的對象可以執(zhí)行相同的行為腋逆,但是他們都需要通過自己的實現(xiàn)方式來執(zhí)行,這就要得益于向上轉(zhuǎn)型了侈贷。
我們都知道所有的類都繼承自超類Object惩歉,toString()方法也是Object中方法等脂,當(dāng)我們這樣寫時:
Object o = new JGJ();
System.out.println(o.toString());
輸出的結(jié)果是Wine : JGJ。
Object撑蚌、Wine上遥、JGJ三者繼承鏈關(guān)系是:JGJ—>Wine—>Object。所以我們可以這樣說:當(dāng)子類重寫父類的方法被調(diào)用時争涌,只有對象繼承鏈中的最末端的方法才會被調(diào)用粉楚。但是注意如果這樣寫:
Object o = new Wine();
System.out.println(o.toString());
輸出的結(jié)果應(yīng)該是Null,因為JGJ并不存在于該對象繼承鏈中亮垫。
所以基于繼承實現(xiàn)的多態(tài)可以總結(jié)如下:對于引用子類的父類類型模软,在處理該引用時,它適用于繼承該父類的所有子類饮潦,子類對象的不同燃异,對方法的實現(xiàn)也就不同,執(zhí)行相同動作產(chǎn)生的行為也就不同继蜡。
如果父類是抽象類回俐,那么子類必須要實現(xiàn)父類中所有的抽象方法,這樣該父類所有的子類一定存在統(tǒng)一的對外接口稀并,但其內(nèi)部的具體實現(xiàn)可以各異仅颇。這樣我們就可以使用頂層類提供的統(tǒng)一接口來處理該層次的方法。
2.2.2碘举、基于接口實現(xiàn)的多態(tài)####
繼承是通過重寫父類的同一方法的幾個不同子類來體現(xiàn)的灵莲,那么就可就是通過實現(xiàn)接口并覆蓋接口中同一方法的幾不同的類體現(xiàn)的。
在接口的多態(tài)中殴俱,指向接口的引用必須是指定這實現(xiàn)了該接口的一個類的實例程序政冻,在運行時,根據(jù)對象引用的實際類型來執(zhí)行對應(yīng)的方法线欲。
繼承都是單繼承明场,只能為一組相關(guān)的類提供一致的服務(wù)接口。但是接口可以是多繼承多實現(xiàn)李丰,它能夠利用一組相關(guān)或者不相關(guān)的接口進(jìn)行組合與擴充苦锨,能夠?qū)ν馓峁┮恢碌姆?wù)接口。所以它相對于繼承來說有更好的靈活性趴泌。
//定義接口InterA
interface InterA
{
void fun();
}
//實現(xiàn)接口InterA的類B
class B implements InterA
{
public void fun()
{
System.out.println(“This is B”);
}
}
//實現(xiàn)接口InterA的類C
class C implements InterA
{
public void fun()
{
System.out.println(“This is C”);
}
}
class Test
{
public static void main(String[] args)
{
//定義接口
InterA a;
//接口類型變量引用實現(xiàn)接口的類的對象
a= new B();
a.fun();
a = new C();
a.fun();
}
}
3舟舒、經(jīng)典實例
通過上面的講述,可以說是對多態(tài)有了一定的了解∈茹荆現(xiàn)在趁熱打鐵秃励,看一個實例。該實例是有關(guān)多態(tài)的經(jīng)典例子吉捶,摘自:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx夺鲜。
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
運行結(jié)果:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
在這里看結(jié)果1皆尔、2、3還好理解币励,從4開始就開始糊涂了慷蠕,對于4來說為什么輸出不是“B and B”呢?
首先我們先看一句話:當(dāng)超類對象引用變量引用子類對象時食呻,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法流炕,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法仅胞。這句話對多態(tài)進(jìn)行了一個概括每辟。其實在繼承鏈中對象方法的調(diào)用存在一個優(yōu)先級:this.show(O)、super.show(O)饼问、this.show((super)O)影兽、super.show((super)O)。
分析:
從上面的程序中我們可以看出A莱革、B峻堰、C、D存在如下關(guān)系盅视。
首先我們分析5捐名,a2.show(c),a2是A類型的引用變量闹击,所以this就代表了A镶蹋,a2.show(c),它在A類中找發(fā)現(xiàn)沒有找到,于是到A的超類中找(super)赏半,由于A沒有超類(Object除外)贺归,所以跳到第三級,也就是this.show((super)O)断箫,C的超類有B拂酣、A,所以(super)O為B仲义、A婶熬,this同樣是A,這里在A中找到了show(A obj)埃撵,同時由于a2是B類的一個引用且B類重寫了show(A obj)赵颅,因此最終會調(diào)用子類B類的show(A obj)方法,結(jié)果也就是B and A暂刘。
按照同樣的方法我也可以確認(rèn)其他的答案饺谬。
方法已經(jīng)找到了但是我們這里還是存在一點疑問,我們還是來看這句話:當(dāng)超類對象引用變量引用子類對象時鸳惯,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法商蕴,但是這個被調(diào)用的方法必須是在超類中定義過的叠萍,也就是說被子類覆蓋的方法芝发。這我們用一個例子來說明這句話所代表的含義:a2.show(b)绪商;
這里a2是引用變量,為A類型辅鲸,它引用的是B對象格郁,因此按照上面那句話的意思是說有B來決定調(diào)用誰的方法,所以a2.show(b)應(yīng)該要調(diào)用B中的show(B obj),產(chǎn)生的結(jié)果應(yīng)該是“B and B”独悴,但是為什么會與前面的運行結(jié)果產(chǎn)生差異呢例书?這里我們忽略了后面那句話“但是這兒被調(diào)用的方法必須是在超類中定義過的”,那么show(B obj)在A類中存在嗎刻炒?根本就不存在决采!所以這句話在這里不適用?那么難道是這句話錯誤了坟奥?非也树瞭!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調(diào)用方法的優(yōu)先級來確認(rèn)。所以它才會在A類中找到show(A obj)爱谁,同時由于B重寫了該方法所以才會調(diào)用B類中的方法晒喷,否則就會調(diào)用A類中的方法。
所以多態(tài)機制遵循的原則概括為:當(dāng)超類對象引用變量引用子類對象時访敌,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法凉敲,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法寺旺,但是它仍然要根據(jù)繼承鏈中方法調(diào)用的優(yōu)先級來確認(rèn)方法爷抓,該優(yōu)先級為:this.show(O)、super.show(O)阻塑、this.show((super)O)蓝撇、super.show((super)O)。
轉(zhuǎn)載自 http://www.cnblogs.com/chenssy/](http://www.cnblogs.com/chenssy/