所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定董虱,而是在程序運行期間才確定,即一個引用變量到底指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法惩坑,必須由程序運行期間才能決定。因為在程序運行時才能確定具體的類也拜,這樣以舒,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上慢哈,從而導致該引用調(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)型存在一些缺憾残腌,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們贫导。所以父類類型的引用可以調(diào)用父類中定義的所有屬性和方法抛猫,對于只存在與子類中的方法和屬性它就望塵莫及了---1。
public class Animal {
public void sleep() {
System.out.println("父類:動物都是要睡覺");
}
}
public class Cat extends Animal {
public void sleep() {
System.out.println("子類:貓跟動物的睡姿是不一樣的");
}
public void action(String name) {
System.out.println("子類: 貓能爬樹");
}
}
public static void main(String args[]) {
Cat cat = new Cat();
cat.sleep();
cat.action();
Animal a2 = new Cat();
a2.sleep();
a2.action();
}
改程序運行錯誤,報錯信息:
Test.java:20: 錯誤: 找不到符號
a2.action();
^
符號: 變量 a2
位置: 類 Test
1 個錯誤
上面的代碼父類a2脱盲,在調(diào)用action()方法的時候邑滨,由于父類沒有action()方法,調(diào)用不了钱反。
多態(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)性逞敷。
Java實現(xiàn)多態(tài)存在三個必要條件:
- 繼承:在多態(tài)中必須存在有繼承關(guān)系的子類和父類
- 重寫:子類對父類中某些方法進行重新定義狂秦,在調(diào)用這些方法時就會調(diào)用子類的方法。
- 向上轉(zhuǎn)型:在多態(tài)中需要將子類的引用賦給父類對象推捐,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法
比如:Parent p = new Child();
只有滿足上述三個條件裂问,我們才能夠在同一個繼承結(jié)構(gòu)中使用同一的邏輯實現(xiàn)代碼處理不同的對象,從而達到執(zhí)行不同的行為玖姑。
對于 Java 而言愕秫,多態(tài)實現(xiàn)機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法焰络,但是這個被調(diào)用的方法必須具有超類中定義過的也就是說唄子類覆蓋的方法。
多態(tài)的機制
多態(tài)的機制分為兩種:
- 編譯時多態(tài)(又稱靜態(tài)多態(tài))
- 運行時多態(tài)(又稱動態(tài)多態(tài))
我們通常所說的多態(tài)指的都是運行時多態(tài)符喝,也就是編譯時不確定究竟調(diào)用哪個具體方法闪彼,一直延遲到運行時才能確定。這也是為什么有時候多態(tài)方法又被稱為延遲方法的原因协饲。
多態(tài)的實現(xiàn)
在Java中有可以使用繼承和接口來實現(xiàn)多態(tài)畏腕。
基于繼承(extends)實現(xiàn)的多態(tài)
基于繼承的實現(xiàn)機制主要表現(xiàn)在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以變現(xiàn)出不同的行為茉稠。
public class Animal {
public void sleep() {
System.out.println("父類:動物都是要睡覺");
}
public void action() {
System.out.println("動物的行為");
}
}
public class Cat extends Animal {
public void sleep() {
System.out.println("子類:貓跟動物的睡姿是不一樣的");
}
public void action(String name) {
System.out.println("子類: 貓能爬樹");
}
}
public static void main(String args[]) {
Animal a2 = new Cat();
a2.sleep();
a2.action();
}
在上面的代碼中父類引用子類對象,調(diào)用的是子類重寫的方法,進行輸出
基于繼承實現(xiàn)的多態(tài)總結(jié):對于引用子類的父類,在處理該引用時,它適用于繼承該父類的所有子類,子類對象的不同,對方法的實現(xiàn)也就不同,執(zhí)行相同動作產(chǎn)生的行為也就不同.
如果父類是抽象類,那么子類必須要實現(xiàn)父類中所有的抽象方法,這樣該父類所有的子類一定存在統(tǒng)一的對外接口,但其內(nèi)部的具體實現(xiàn)可以各異.這樣我們就可以使用頂層類提供的統(tǒng)一接口來處理該層次的方法.
基于接口實現(xiàn)的多態(tài)
繼承是通過重寫父類的同一方法的幾個不同子類來體現(xiàn)的,那么接口就是通過實現(xiàn)接口并覆蓋接口中同一方法的不同的類的體現(xiàn).
在接口的多態(tài)中.指向接口的引用必須是指定這實現(xiàn)了該接口的一個類的實例程序,在運行時,根據(jù)對象引用的實際類型來執(zhí)行對應的方法
繼承都是單繼承,只能為一組相關(guān)的類提供一致的服務接口,但是接口可以是多繼承多實現(xiàn),它能夠利用一組相關(guān)或者不相關(guān)的接口進行組合與擴充,能夠?qū)ν馓峁┮恢碌姆战涌?所以它相對于繼承來說有更好的靈活性.
多態(tài)的優(yōu)點
- 消除類型之間的耦合關(guān)系
- 可替換性(substitutability): 多態(tài)對已存在代碼具有可替換性.
- 可擴充性
- 接口性
- 靈活性
- 簡化性
當使用多態(tài)方式調(diào)用方法時描馅,首先檢查父類中是否有該方法,如果沒有而线,則編譯錯誤铭污;如果有恋日,在去調(diào)用子類的同同名方法。
多態(tài)的好處:可以使程序有良好的擴展嘹狞,并可以對所有類的對象進行通用處理岂膳。
經(jīng)典實例
class A{
public String show(D obj){
return ("A and D");
}
public String show(A obj){
return ("A abd A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{ }
class D extends B{ }
問題:以下輸出結(jié)果是什么?
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();
}
}
運行結(jié)果
System.out.println(a1.show(b)); ① A and A
System.out.println(a1.show(c)); ② A and A
System.out.println(a1.show(d)); ③ A and D
System.out.println(a2.show(b)); ④ B and A
System.out.println(a2.show(c)); ⑤ B and A
System.out.println(a2.show(d)); ⑥ A and D
System.out.println(b.show(b)); ⑦ B and B
System.out.println(b.show(c)); ⑧ B and B
System.out.println(b.show(d)); ⑨ A and D