面向對象編程有三大特性:封裝、繼承、多態。編程
封裝隱藏了類的內部實現機制,能夠在不影響使用的狀況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。數組
繼承是爲了重用父類代碼。兩個類若存在IS-A的關係就可使用繼承。,同時繼承也爲實現多態作了鋪墊。那麼什麼是多態呢?多態的實現機制又是什麼?請看我一一爲你揭開:函數
所謂多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。this
好比你是一個酒神,對酒情有獨鍾。某日回家發現桌上有幾個杯子裏面都裝了白酒,從外面看咱們是不可能知道這是些什麼酒,只有喝了以後纔可以猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這裏咱們能夠描述成以下:spa
酒 a = 劍南春.net
酒 b = 五糧液code
酒 c = 酒鬼酒對象
…blog
這裏所表現的的就是多態。劍南春、五糧液、酒鬼酒都是酒的子類,咱們只是經過酒這一個父類就可以引用不一樣的子類,這就是多態——咱們只有在運行的時候纔會知道引用變量所指向的具體實例對象。繼承
誠然,要理解多態咱們就必需要明白什麼是「向上轉型」。在繼承中咱們簡單介紹了向上轉型,這裏就在囉嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。咱們定義以下代碼:
JNC a = new JNC();
對於這個代碼咱們很是容易理解無非就是實例化了一個劍南春的對象嘛!可是這樣呢?
Wine a = new JNC();
在這裏咱們這樣理解,這裏定義了一個Wine 類型的a,它指向JNC對象實例。因爲JNC是繼承與Wine,因此JNC能夠自動向上轉型爲Wine,因此a是能夠指向JNC實例對象的。這樣作存在一個很是大的好處,在繼承中咱們知道子類是父類的擴展,它能夠提供比父類更增強大的功能,若是咱們定義了一個指向子類的父類引用類型,那麼它除了可以引用父類的共性外,還可使用子類強大的功能。
可是向上轉型存在一些缺憾,那就是它一定會致使一些方法和屬性的丟失,而致使咱們不可以獲取它們。因此父類類型的引用能夠調用父類中定義的全部屬性和方法,對於只存在與子類中的方法和屬性它就可望不可即了---1。
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 子類重載父類方法 * 父類中不存在該方法,向上轉型後,父類是不能引用該方法的 * @param a * @return void */ public void fun1(String a){ System.out.println("JNC 的 Fun1..."); fun2(); } /** * 子類重寫父類方法 * 指向子類的父類引用調用fun2時,一定是調用該方法 */ 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...
從程序的運行結果中咱們發現,a.fun1()首先是運行父類Wine中的fun1().而後再運行子類JNC中的fun2()。
分析:在這個程序中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),並且重載後的fun1(String a)與 fun1()不是同一個方法,因爲父類中沒有該方法,向上轉型後會丟失該方法,因此執行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那麼指向JNC的Wine引用會調用JNC中fun2()方法。
因此對於多態咱們能夠總結以下:
指向子類的父類引用因爲向上轉型了,它只能訪問父類中擁有的方法和屬性,而對於子類中存在而父類中不存在的方法,該引用是不能使用的,儘管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,一定是使用子類中定義的這些方法(動態鏈接、動態調用)。
對於面向對象而已,多態分爲編譯時多態和運行時多態。其中編輯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不一樣來區分不一樣的函數,經過編輯以後會變成兩個不一樣的函數,在運行時談不上多態。而運行時多態是動態的,它是經過動態綁定來實現的,也就是咱們所說的多態性。
2.1實現條件
在剛剛開始就提到了繼承在爲多態的實現作了準備。子類Child繼承父類Father,咱們能夠編寫一個指向子類的父類類型引用,該引用既能夠處理父類Father對象,也能夠處理子類Child對象,當相同的消息發送給子類或者父類對象時,該對象就會根據本身所屬的引用而執行不一樣的行爲,這就是多態。即多態性就是相同的消息使得不一樣的類作出不一樣的響應。
Java實現多態有三個必要條件:繼承、重寫、向上轉型。
繼承:在多態中必須存在有繼承關係的子類和父類。
重寫:子類對父類中某些方法進行從新定義,在調用這些方法時就會調用子類的方法。
向上轉型:在多態中須要將子類的引用賦給父類對象,只有這樣該引用纔可以具有技能調用父類的方法和子類的方法。
只有知足了上述三個條件,咱們纔可以在同一個繼承結構中使用統一的邏輯實現代碼處理不一樣的對象,從而達到執行不一樣的行爲。
對於Java而言,它多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
2.2實現形式
在Java中有兩種形式能夠實現多態。繼承和接口。
2.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 -------------------------------
在上面的代碼中JNC、JGJ繼承Wine,而且重寫了drink()、toString()方法,程序運行結果是調用子類中方法,輸出JNC、JGJ的名稱,這就是多態的表現。不一樣的對象能夠執行相同的行爲,可是他們都須要經過本身的實現方式來執行,這就要得益於向上轉型了。
咱們都知道全部的類都繼承自超類Object,toString()方法也是Object中方法,當咱們這樣寫時:
Object o = new JGJ(); System.out.println(o.toString());
輸出的結果是Wine : JGJ。
Object、Wine、JGJ三者繼承鏈關係是:JGJ—>Wine—>Object。因此咱們能夠這樣說:當子類重寫父類的方法被調用時,只有對象繼承鏈中的最末端的方法纔會被調用。可是注意若是這樣寫:
Object o = new Wine(); System.out.println(o.toString());
輸出的結果應該是Null,由於JGJ並不存在於該對象繼承鏈中。
因此基於繼承實現的多態能夠總結以下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的全部子類,子類對象的不一樣,對方法的實現也就不一樣,執行相同動做產生的行爲也就不一樣。
若是父類是抽象類,那麼子類必需要實現父類中全部的抽象方法,這樣該父類全部的子類必定存在統一的對外接口,但其內部的具體實現能夠各異。這樣咱們就可使用頂層類提供的統一接口來處理該層次的方法。
2.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
在這裏看結果一、二、3還好理解,從4開始就開始糊塗了,對於4來講爲何輸出不是「B and B」呢?
首先咱們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這句話對多態進行了一個歸納。其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
分析:
從上面的程序中咱們能夠看出A、B、C、D存在以下關係。
首先咱們分析5,a2.show(c),a2是A類型的引用變量,因此this就表明了A,a2.show(c),它在A類中找發現沒有找到,因而到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),所以最終會調用子類B類的show(A obj)方法,結果也就是B and A。
按照一樣的方法我也能夠確認其餘的答案。
方法已經找到了可是咱們這裏仍是存在一點疑問,咱們仍是來看這句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這咱們用一個例子來講明這句話所表明的含義:a2.show(b);
這裏a2是引用變量,爲A類型,它引用的是B對象,所以按照上面那句話的意思是說有B來決定調用誰的方法,因此a2.show(b)應該要調用B中的show(B obj),產生的結果應該是「B and B」,可是爲何會與前面的運行結果產生差別呢?這裏咱們忽略了後面那句話「可是這兒被調用的方法必須是在超類中定義過的」,那麼show(B obj)在A類中存在嗎?根本就不存在!因此這句話在這裏不適用?那麼難道是這句話錯誤了?非也!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調用方法的優先級來確認。因此它纔會在A類中找到show(A obj),同時因爲B重寫了該方法因此纔會調用B類中的方法,不然就會調用A類中的方法。
因此多態機制遵循的原則歸納爲:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,可是它仍然要根據繼承鏈中方法調用的優先級來確認方法,該優先級爲:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。