面向對象編程有三大特性:封裝、繼承、多態。
封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。
繼承是為了重用父類代碼。兩個類若存在IS-A的關系就可以使用繼承。同時繼承也為實現多態做了鋪墊。那么什么是多態呢?多態的實現機制又是什么?請看我一一為你揭開。
多態
所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。
比如你是一個酒神,對酒情有獨鍾。某日回家發現桌上有幾個杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒,只有喝了之后才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:
酒 a = 劍南春
酒 b = 五糧液
酒 c = 酒鬼酒
…
這里所表現的的就是多態。劍南春、五糧液、酒鬼酒都是酒的子類,我們只是通過酒這一個父類就能夠引用不同的子類,這就是多態——我們只有在運行的時候才會知道引用變量所指向的具體實例對象。
誠然,要理解多態我們就必須要明白什么是“向上轉型”。在繼承中我們簡單介紹了向上轉型,這里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:
JNC a = new JNC();
對於這個代碼我們非常容易理解無非就是實例化了一個劍南春的對象嘛!但是這樣呢?
Wine a = new JNC();
在這里我們這樣理解,這里定義了一個Wine 類型的a,它指向JNC對象實例。由於JNC是繼承與Wine,所以JNC可以自動向上轉型為Wine,所以a是可以指向JNC實例對象的。這樣做存在一個非常大的好處,在繼承中我們知道子類是父類的擴展,它可以提供比父類更加強大的功能,如果我們定義了一個指向子類的父類引用類型,那么它除了能夠引用父類的共性外,還可以使用子類強大的功能。
但是向上轉型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調用父類中定義的所有屬性和方法,對於只存在與子類中的方法和屬性它就望塵莫及了---1。
1 public class Wine { 2 public void fun1(){ 3 System.out.println("Wine 的Fun....."); 4 fun2(); 5 } 6 7 public void fun2(){ 8 System.out.println("Wine 的Fun2..."); 9 } 10 } 11 12 public class JNC extends Wine{ 13 /** 14 * @desc 子類重寫父類方法 15 * 父類中不存在該方法,向上轉型后,父類是不能引用該方法的 16 * @param a 17 * @return void 18 */ 19 public void fun1(String a){ 20 System.out.println("JNC 的 Fun1..."); 21 fun2(); 22 } 23 24 /** 25 * 子類重寫父類方法 26 * 指向子類的父類引用調用fun2時,必定是調用該方法 27 */ 28 public void fun2(){ 29 System.out.println("JNC 的Fun2..."); 30 } 31 } 32 33 public class Test { 34 public static void main(String[] args) { 35 Wine a = new JNC(); 36 a.fun1(); 37 } 38 } 39 ------------------------------------------------- 40 Output: 41 Wine 的Fun..... 42 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()方法。
所以對於多態我們可以總結如下:
指向子類的父類引用由於向上轉型了,它只能訪問父類中擁有的方法和屬性,而對於子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態連接、動態調用)。
多態的實現
實現條件
在剛剛開始就提到了繼承在為多態的實現做了准備。子類Child繼承父類Father,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類Father對象,也可以處理子類Child對象,當相同的消息發送給子類或者父類對象時,該對象就會根據自己所屬的引用而執行不同的行為,這就是多態。即多態性就是相同的消息使得不同的類做出不同的響應。
Java實現多態有三個必要條件:繼承、重寫、向上轉型。
繼承:在多態中必須存在有繼承關系的子類和父類。
重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。
只有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統一的邏輯實現代碼處理不同的對象,從而達到執行不同的行為。
對於Java而言,它多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
實現形式
在Java中有兩種形式可以實現多態。繼承和接口。
基於繼承實現的多態
基於繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行為。
1 public class Wine { 2 private String name; 3 4 public String getName() { 5 return name; 6 } 7 8 public void setName(String name) { 9 this.name = name; 10 } 11 12 public Wine(){ 13 } 14 15 public String drink(){ 16 return "喝的是 " + getName(); 17 } 18 19 /** 20 * 重寫toString() 21 */ 22 public String toString(){ 23 return null; 24 } 25 } 26 27 public class JNC extends Wine{ 28 public JNC(){ 29 setName("JNC"); 30 } 31 32 /** 33 * 重寫父類方法,實現多態 34 */ 35 public String drink(){ 36 return "喝的是 " + getName(); 37 } 38 39 /** 40 * 重寫toString() 41 */ 42 public String toString(){ 43 return "Wine : " + getName(); 44 } 45 } 46 47 public class JGJ extends Wine{ 48 public JGJ(){ 49 setName("JGJ"); 50 } 51 52 /** 53 * 重寫父類方法,實現多態 54 */ 55 public String drink(){ 56 return "喝的是 " + getName(); 57 } 58 59 /** 60 * 重寫toString() 61 */ 62 public String toString(){ 63 return "Wine : " + getName(); 64 } 65 } 66 67 public class Test { 68 public static void main(String[] args) { 69 //定義父類數組 70 Wine[] wines = new Wine[2]; 71 //定義兩個子類 72 JNC jnc = new JNC(); 73 JGJ jgj = new JGJ(); 74 75 //父類引用子類對象 76 wines[0] = jnc; 77 wines[1] = jgj; 78 79 for(int i = 0 ; i < 2 ; i++){ 80 System.out.println(wines[i].toString() + "--" + wines[i].drink()); 81 } 82 System.out.println("-------------------------------"); 83 84 } 85 } 86 OUTPUT: 87 Wine : JNC--喝的是 JNC 88 Wine : JGJ--喝的是 JGJ 89 -------------------------------
在上面的代碼中JNC、JGJ繼承Wine,並且重寫了drink()、toString()方法,程序運行結果是調用子類中方法,輸出JNC、JGJ的名稱,這就是多態的表現。不同的對象可以執行相同的行為,但是他們都需要通過自己的實現方式來執行,這就要得益於向上轉型了。
所以基於繼承實現的多態可以總結如下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的所有子類,子類對象的不同,對方法的實現也就不同,執行相同動作產生的行為也就不同。
如果父類是抽象類,那么子類必須要實現父類中所有的抽象方法,這樣該父類所有的子類一定存在統一的對外接口,但其內部的具體實現可以各異。這樣我們就可以使用頂層類提供的統一接口來處理該層次的方法。
基於接口實現的多態
繼承是通過重寫父類的同一方法的幾個不同子類來體現的,那么就可就是通過實現接口並覆蓋接口中同一方法的幾不同的類體現的。
在接口的多態中,指向接口的引用必須是指定這實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。
繼承都是單繼承,只能為一組相關的類提供一致的服務接口。但是接口可以是多繼承多實現,它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一致的服務接口。所以它相對於繼承來說有更好的靈活性。
如 Map map =new HashMap ; List list=new ArraryList 都是基於接口實現的多態。
經典實例
1 public class A { 2 public String show(D obj) { 3 return ("A and D"); 4 } 5 6 public String show(A obj) { 7 return ("A and A"); 8 } 9 10 } 11 12 public class B extends A{ 13 public String show(B obj){ 14 return ("B and B"); 15 } 16 17 public String show(A obj){ 18 return ("B and A"); 19 } 20 } 21 22 public class C extends B{ 23 24 } 25 26 public class D extends B{ 27 28 } 29 30 public class Test { 31 public static void main(String[] args) { 32 A a1 = new A(); 33 A a2 = new B(); 34 B b = new B(); 35 C c = new C(); 36 D d = new D(); 37 38 System.out.println("1--" + a1.show(b)); 39 System.out.println("2--" + a1.show(c)); 40 System.out.println("3--" + a1.show(d)); 41 System.out.println("4--" + a2.show(b)); 42 System.out.println("5--" + a2.show(c)); 43 System.out.println("6--" + a2.show(d)); 44 System.out.println("7--" + b.show(b)); 45 System.out.println("8--" + b.show(c)); 46 System.out.println("9--" + b.show(d)); 47 } 48 }
運行結果:
1 1--A and A 2 2--A and A 3 3--A and D 4 4--B and A 5 5--B and A 6 6--A and D 7 7--B and B 8 8--B and B 9 9--A and D