所謂多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。html
1.1實現條件編程
在剛剛開始就提到了繼承在爲多態的實現作了準備。子類Child繼承父類Father,咱們能夠編寫一個指向子類的父類類型引用,該引用既能夠處理父類Father對象,也能夠處理子類Child對象,當相同的消息發送給子類或者父類對象時,該對象就會根據本身所屬的引用而執行不一樣的行爲,這就是多態。即多態性就是相同的消息使得不一樣的類作出不一樣的響應。數組
Java實現多態有三個必要條件:繼承、重寫、向上轉型。字體
繼承:在多態中必須存在有繼承關係的子類和父類。this
重寫:子類對父類中某些方法進行從新定義,在調用這些方法時就會調用子類的方法。spa
向上轉型:在多態中須要將子類的引用賦給父類對象,只有這樣該引用纔可以具有技能調用父類的方法和子類的方法。.net
只有知足了上述三個條件,咱們纔可以在同一個繼承結構中使用統一的邏輯實現代碼處理不一樣的對象,從而達到執行不一樣的行爲。設計
對於Java而言,它多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。code
1.2實現形式htm
在Java中有兩種形式能夠實現多態。繼承和接口。
1.2.一、基於繼承實現的多態
基於繼承的實現機制主要表如今父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫能夠表現出不一樣的行爲。
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"); } /** * 重寫父類方法,實現多態 */ public String drink(){ return "喝的是 " + getName(); } /** * 重寫toString() */ public String toString(){ return "Wine : " + getName(); } } public class JGJ extends Wine{ public JGJ(){ setName("JGJ"); } /** * 重寫父類方法,實現多態 */ public String drink(){ return "喝的是 " + getName(); } /** * 重寫toString() */ public String toString(){ return "Wine : " + getName(); } } public class Test { public static void main(String[] args) { //定義父類數組 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 -------------------------------
因此基於繼承實現的多態能夠總結以下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的全部子類,子類對象的不一樣,對方法的實現也就不一樣,執行相同動做產生的行爲也就不一樣。
若是父類是抽象類,那麼子類必需要實現父類中全部的抽象方法,這樣該父類全部的子類必定存在統一的對外接口,但其內部的具體實現能夠各異。這樣咱們就可使用頂層類提供的統一接口來處理該層次的方法。
1.2.二、基於接口實現的多態
繼承是經過重寫父類的同一方法的幾個不一樣子類來體現的,那麼就可就是經過實現接口並覆蓋接口中同一方法的幾不一樣的類體現的。
在接口的多態中,指向接口的引用必須是指定這實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。
繼承都是單繼承,只能爲一組相關的類提供一致的服務接口。可是接口能夠是多繼承多實現,它可以利用一組相關或者不相關的接口進行組合與擴充,可以對外提供一致的服務接口。因此它相對於繼承來講有更好的靈活性。
經過上面的講述,能夠說是對多態有了必定的瞭解。如今趁熱打鐵,看一個實例。該實例是有關多態的經典例子,摘自: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)); } }
運行結果:
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
①②③比較好理解,通常不會出錯。④⑤就有點糊塗了,爲何輸出的不是"B and B」呢?!!先來回顧一下多態性。
運行時多態性是面向對象程序設計代碼重用的一個最強大機制,動態性的概念也能夠被說成「一個接口,多個方法」。Java實現運行時多態性的基礎是動態方法調度,它是一種在運行時而不是在編譯期調用重載方法的機制。
方法的重寫Overriding和重載Overloading是Java多態性的不一樣表現。重寫Overriding是父類與子類之間多態性的一種表現,重載Overloading是一個類中多態性的一種表現。若是在子類中定義某方法與其父類有相同的名稱和參數,咱們說該方法被重寫(Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被「屏蔽」了。若是在一個類中定義了多個同名的方法,它們或有不一樣的參數個數或有不一樣的參數類型,則稱爲方法的重載(Overloading)。Overloaded的方法是能夠改變返回值的類型。
當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。 (可是若是強制把超類轉換成子類的話,就能夠調用子類中新添加而超類沒有的方法了。)
好了,先溫習到這裏,言歸正傳!實際上這裏涉及方法調用的優先問題 ,優先級由高到低依次爲:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。讓咱們來看看它是怎麼工做的。
好比④,a2.show(b),a2是一個引用變量,類型爲A,則this爲a2,b是B的一個實例,因而它到類A裏面找show(B obj)方法,沒有找到,因而到A的super(超類)找,而A沒有超類,所以轉到第三優先級this.show((super)O),this仍然是a2,這裏O爲B,(super)O即(super)B即A,所以它到類A裏面找show(A obj)的方法,類A有這個方法,可是因爲a2引用的是類B的一個對象,B覆蓋了A的show(A obj)方法,所以最終鎖定到類B的show(A obj),輸出爲"B and A」。
再好比⑧,b.show(c),b是一個引用變量,類型爲B,則this爲b,c是C的一個實例,因而它到類B找show(C obj)方法,沒有找到,轉而到B的超類A裏面找,A裏面也沒有,所以也轉到第三優先級this.show((super)O),this爲b,O爲C,(super)O即(super)C即B,所以它到B裏面找show(B obj)方法,找到了,因爲b引用的是類B的一個對象,所以直接鎖定到類B的show(B obj),輸出爲"B and B」。
按照上面的方法,能夠正確獲得其餘的結果。
問題還要繼續,如今咱們再來看上面的分析過程是怎麼體現出藍色字體那句話的內涵的。它說:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。仍是拿a2.show(b)來講吧。
a2是一個引用變量,類型爲A,它引用的是B的一個對象,所以這句話的意思是由B來決定調用的是哪一個方法。所以應該調用B的show(B obj)從而輸出"B and B」纔對。可是爲何跟前面的分析獲得的結果不相符呢?!問題在於咱們不要忽略了藍色字體的後半部分,那裏特別指明:這個被調用的方法必須是在超類中定義過的,也就是被子類覆蓋的方法。B裏面的show(B obj)在超類A中有定義嗎?沒有!那就更談不上被覆蓋了。實際上這句話隱藏了一條信息:它仍然是按照方法調用的優先級來肯定的。它在類A中找到了show(A obj),若是子類B沒有覆蓋show(A obj)方法,那麼它就調用A的show(A obj)(因爲B繼承A,雖然沒有覆蓋這個方法,但從超類A那裏繼承了這個方法,從某種意義上說,仍是由B肯定調用的方法,只是方法是在A中實現而已);如今子類B覆蓋了show(A obj),所以它最終鎖定到B的show(A obj)。這就是那句話的意義所在。
參考資料:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。
百度文庫:http://wenku.baidu.com/view/73f66f92daef5ef7ba0d3c03.html