菜雞的Java課筆記 第二十二 物件多型性
本次只是圍繞著多型性的概念來進行講解,但是所講解的程式碼與實際的開發幾乎沒有關係,而且多型一定是在繼承性的基礎上才可以操作的,
而本次將使用類繼承的關係來描述多型的性質,實際的開發中不會出現普通類的繼承關係(一個已經完善的類不應該再被繼承),開發中都要求繼承抽象類和介面
多型性要想實現有兩個前提:繼承,覆寫
範例:引出程式碼
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } } public class polymorphism{ public static void main(String args[]){ B b = new B(); b.print(); } }
覆寫呼叫的前提:看new 的是那個類的物件,而後看方法是否被子類所覆寫
在java中多型性主要由兩個方面組成:
方法的多型性:
方法過載:方法呼叫時根據不同的引數個數及型別可以實現不同的功能
方法覆寫:不同的子類針對於同樣的一個方法可以有不同的實現
物件的多型性:父類與子類物件間的轉換操作;發生在繼承關係之中
【自動】物件的向上轉型:子類物件變為父類物件 父類 父類物件 = 子類例項,自動完成的
【強制】物件的向下轉型;父類物件變為子類物件 子類 子類物件 = (子類)父類例項,強制轉換
除了轉型之外,還有一些操作是不轉型的,例如:String
物件多型性基礎實現
觀察一道程式
範例:
class Member{ public String getInfo(){ return "Member:我是一個會員"; } } class VIPMember extends Member{ // 產生繼承 public String getInfo(){ return "VIPMember:我是一個貴賓會員"; } } public class polymorphism{ public static void main(String args[]){ VIPMember mam = new VIPMember(); System.out.println(mem.getInfo()); } }
方法覆寫觀察:
觀察現在 new 的是那個子類
觀察這個子類呼叫的方法是否被覆寫,如果被覆寫了則呼叫的就是被覆寫過的方法
範例:物件的向上轉型
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } } public class polymorphism{ public static void main(String args[]){ A a = new B(); // 向上轉型 a.print(); } }
可以發現向上轉型是自動完成的,除了向上轉型之外,也可以實現物件的向下轉型操作
向上轉型最大的特點在於:所有的子類物件按照統一的父類型別進行接收,但是由於例項化子類的不同,有可能同一個父類的方法會有不同的呼叫實現
為了更好的理解物件的向上轉型問題就做一個簡單的分析:要求設計一個方法,這個方法可以接收使用者的引數資訊
如果這個時候採用原始的技術:方法過載
範例:
class Member{ public String getInfo(){ return "Member:我是一個會員"; } } class VIPMember extends Member{ // 產生繼承 public String getInfo(){ return "VIPMember:我是一個貴賓會員"; } } class CIPMember extends Member{ // 產生繼承 public String getInfo(){ return "CIPMember:我是一個商務會員"; } } public class polymorphism{ public static void main(String args[]){ income(new Member()); income(new VIPMember()); income(new CIPMember()); } public static void income(Member mem){ System.out.println(mem.getInfo()); } public static void income(VIPMember mem){ System.out.println(mem.getInfo()); } public static void income(CIPMember mem){ System.out.println(mem.getInfo()); } }
缺點:
如果突然有一天你的使用者型別分為了十萬種,那麼就需要有十萬個 Member 子類,這個方法會過載十萬次
所有的方法體執行的功能都一樣,那麼這樣是一個明顯重複
當子類眾多又需要考慮引數統一的時候,按惡魔最好用的做法就是進行物件的向上轉型(自動,呼叫被覆寫過的方法)
class Member{ public String getInfo(){ return "Member:我是一個會員"; } } class VIPMember extends Member{ // 產生繼承 public String getInfo(){ return "VIPMember:我是一個貴賓會員"; } } class CIPMember extends Member{ // 產生繼承 public String getInfo(){ return "CIPMember:我是一個商務會員"; } } public class polymorphism{ public static void main(String args[]){ income(new Member()); income(new VIPMember()); income(new CIPMember()); } public static void income(Member mem){ System.out.println(mem.getInfo()); } }
發現利用物件的向上轉型可以有效的實現引數的統一處理。這一點在程式中至關重要
物件向下轉型
範例:物件的向下轉型
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } } public class polymorphism{ public static void main(String args[]){ A a = new B(); // 向上轉型 B b = (B)a; // 向下轉型 b.print(); } }
具體的轉型的概念沒有什麼難理解,那麼程式的執行結果也很好理解,但是這樣做有什麼意義呢?
分析:向上轉型的意義
現在要求定義一個 fun()方法,這個方法要求可以接收A以及A類所有子類的例項化物件
於是根據這樣的描述可以有兩種實現方案
方案一:使用方法過載的概念來完成
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } } class C extends A{ public void print(){ System.out.println("不好!"); } } public class polymorphism{ public static void main(String args[]){ fun(new A()); fun(new B()); fun(new C()); } public static void fun(A a){ a.print(); } public static void fun(B b){ b.print(); } public static void fun(C c){ c.print(); } } /* 結果: Hello 你好! 不好 */
如果說這個時候A類有3000W個子類,並且這個子類還在以倍數的方式增長
那麼就表示:在設計之初此方法就需要過載3000W次,並且隨著子類的追加,此方法繼續過載,繼續不斷的修改類,於是你就瘋了......
方案二:發現所有的子類呼叫的方法實際上都只是println()一個,那麼現在可以利用物件自動向上轉型的概念就直接使用A類接收
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } } class C extends A{ public void print(){ System.out.println("不好!"); } } public class polymorphism{ public static void main(String args[]){ fun(new A()); fun(new B()); fun(new C()); } public static void fun(A a){ a.print(); } } /* 結果: Hello 你好! 不好 */
所以物件的向上轉型給開發者最大的幫助在於其資料操作的統一性上
分析:向下轉型的意義
範例:觀察問題
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a = new B(); // 向上轉型 a.print(); a.funB();// 不能夠呼叫 } } //結果:出錯
一旦發生了向上轉型之後,父類物件是不可能呼叫子類中新建的方法的,只能夠呼叫父類自己本身所定義的方法名稱,也就是說向上轉型之後犧牲的是子類的個性化特徵
但是如果說現在要想呼叫子類定義的特殊方法,那麼就必須採用向下轉型
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a = new B(); // 向上轉型 (把A改為B??) a.print(); B b = (B)a; b.funB(); } } /* 結果: 你好! ****************** */
解釋:為什麼現在你的程式碼裡面需要先向上轉型,在進行向下轉型,囉嗦
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } class C extends A{ public void print(){ System.out.println("不好!"); } public void funC(){ // 子類自己擴充的新方法 System.out.println("###################"); } } public class polymorphism{ public static void main(String args[]){ fun(new B()); fun(new C()); } public static void fun(A a){ a.print() // 由於某種特殊需求必須呼叫B類中的funB()方法,所以要進行向下轉型 B b = (B)a; b.funB(); } } /* 結果: 你好! 不好 */
現在如果使用了向下轉型,那麼在之前好不容易建立起來的引數統一的局面就被打破了,所以這樣的操作就屬於不合理的操作形式
但是轉型的意義卻可以更加明確了:為了呼叫子類自己的特殊支援
但是在進行物件向下轉型前也需要有一個注意點:
範例:錯誤的向下轉型
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a == new A(); // 父類物件 B b = (B)a;// 強制轉換 b.print() } } //結果:出錯
此時出現了“java.langClassException”異常資訊,,表示的是類轉換異常,本質指的是兩個沒有關係的類物件發生了強制轉換所帶來的問題
所以要想進行向下轉型操作之前一定要首先保證發生了向上轉型,這樣才可以建立父子物件的關係
但是可以發現這樣的轉型本身是會存在有安全隱患的,所以正在java中提供有一個關鍵字:instanceof,利用此關鍵字可以判斷某一個物件是否是指定類的例項
物件 instanceof 類 返回boolean型別
範例:觀察instanceof關鍵字的使用
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a == new A(); // 父類物件 System.out.println(a instanceof A); System.out.println(a instanceof B); //B b = (B)a;// 強制轉換 //b.print() } } /* 結果: true false */
範例:如果發生了向上轉型之後的判斷
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a == new B(); // 父類物件 System.out.println(a instanceof A); System.out.println(a instanceof B); //B b = (B)a;// 強制轉換 //b.print() } } /* 結果: true true */
範例:利用 instanceof 保證轉型的正確性
class A { public void print(){ System.out.println("hello"); } } class B extends A{ public void print(){ System.out.println("你好!"); } public void funB(){ // 子類自己擴充的新方法 System.out.println("********************"); } } public class polymorphism{ public static void main(String args[]){ A a == new B(); // 父類物件 System.out.println(a instanceof A); System.out.println(a instanceof B); if(a instanceof B){ B b = (B)a;// 強制轉換 b.print() } } } /* 結果: true true 你好 */
總結
向上轉型(90%):為了實現引數型別的統一,但是向上轉型一定要與方法覆寫產生關聯
向上轉型(1%):為了呼叫子類特殊的方法實現,但是向下轉型前必須要首先發生向上轉型,會存在操作的安全性隱患,可以使用 instanceof 進行判斷,但是不推薦這樣使用
不轉型(9%):為了方便操作直接使用系統類或者是一些功能類,例如String 簡單java類