1、多態介紹 面向對象三大特征:封裝、繼承、多態。多態是Java面向對象最核心,最難以理解的內容。從一定角度來看,封裝和繼承幾乎都是為多態而準備的。 多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編譯時並不確定,而是在程式運行期間才確定,即一個引用變數倒底會指向哪個類 ...
1、多態介紹
面向對象三大特征:封裝、繼承、多態。多態是Java面向對象最核心,最難以理解的內容。從一定角度來看,封裝和繼承幾乎都是為多態而準備的。
多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編譯時並不確定,而是在程式運行期間才確定,即一個引用變數倒底會指向哪個類的實例對象,該引用變數發出的方法調用到底是哪個類中實現的方法,必須在由程式運行期間才能決定。因為在程式運行時才確定具體的類,這樣,不用修改源程式代碼,就可以讓引用變數綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程式代碼就可以改變程式運行時所綁定的具體代碼,讓程式可以選擇多個運行狀態,這就是多態性。
比如有一種動物,藏在某個看不見得地方,你知道它是一種動物,但是不知道具體是哪種動物,只有等它發出叫聲才能辨別,是貓——喵喵喵,是狗——旺旺旺,你一聽就知道這是什麼動物,對於不同的動物會有不同的結果,可以理解為多態。
Java多態也可以用一句話表示:父類的引用指向子類的對象。
多態存在的三個必要條件
1、繼承:在多態中必須存在有繼承關係的子類和父類。
2、重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
3、向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。
2、多態的實現
開始講了這麼多Java多態的基本概念,那麼到底用Java代碼怎麼體現出來呢?下麵我們直接看代碼。
public class Animal { public void eat(){ System.out.println("動物吃東西"); } public void skill(){ System.out.println("動物有本領"); } } class Dog extends Animal{ public void eat() { System.out.println("狗吃骨頭"); } public void skill(){ System.out.println("狗會看家"); } } class Cat extends Animal{ public void eat() { System.out.println("貓吃魚"); } public void skill(){ System.out.println("貓會抓老鼠"); } } //測試類 public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog(); dog.eat(); dog.skill(); Animal cat=new Cat(); cat.eat(); cat.skill(); } }
運行結果:
從上面的測試類來看,我們都是創建不同子類的對象,相同的父類引用,卻表現出它們不同的特征,這就是體現了Java的多態性。
如果你認為這樣還是不能體現多態的好處,我們在AnimalTest類中添加一個show()方法,來體會Java多態的好處。
如果Java中沒有多態的特性會是什麼樣的,下麵我們來看一下。
public class AnimalTest { public static void main(String[] args) { AnimalTest animalTest=new AnimalTest(); animalTest.show(new Animal()); animalTest.show(new Dog()); animalTest.show(new Cat()); } public void show(Animal animal){ animal.eat(); animal.skill(); } public void show(Dog dog){ dog.eat(); dog.skill(); } public void show(Cat cat){ cat.eat(); cat.skill(); } }
可以發現在show()方法中的形參都傳入了對象,而且重載3個相同的show()方法,如果我們有十個百個千個類需要傳入方法,那麼豈不是要重載上千個方法,可見這樣代碼的冗餘非常的大,非常不利於代碼的維護。
而如果有多態的話,只需寫一個show()方法即可。
public class AnimalTest { public static void main(String[] args) { AnimalTest animalTest=new AnimalTest(); animalTest.show(new Animal()); animalTest.show(new Dog()); animalTest.show(new Cat()); } public void show(Animal animal){ animal.eat(); animal.skill(); } }
從這裡可以看出來多態的優點:
1、減少重覆代碼,使代碼變得簡潔(由繼承保證)。
2、提高了代碼的擴展性(由多態保證)。
但是也有缺點:子類單獨定義的方法會丟失。後面的向上轉型會介紹到。
多態其實是一種虛擬方法調用。在編譯期間,只能調用父類中聲明的方法,但是在運行期間,實際執行的是子類重寫父類的方法。
總結為一句話:編譯看左邊,運行看右邊(可能看到這句話會有點頭暈,但是理解下麵向上轉型的概念就應該能夠理解這句話了)。
3、向上轉型
子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是將子類對象轉為父類對象。此處父類對象也可以是介面。
我們用前面多態的例子舉例,只是在子類中添加了它們自己的方法,父類中沒有定義,如下。
public class Animal { public void eat(){ System.out.println("動物吃東西"); } public void skill(){ System.out.println("動物有本領"); } } class Dog extends Animal{ public void eat() { System.out.println("狗吃骨頭"); } public void skill(){ System.out.println("狗會看家"); } //新添加的方法 public void run(){ System.out.println("狗跑得快"); } } class Cat extends Animal{ public void eat() { System.out.println("貓吃魚"); } public void skill(){ System.out.println("貓會抓老鼠"); } //新添加的方法 public void life(){ System.out.println("貓有九條命"); } } //測試類 public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog();//向上轉型成Animal dog.eat(); dog.skill(); //dog.run();//Cannot resolve method 'run()' Animal cat = new Cat();//向上轉型成Animal cat.eat(); cat.skill(); //cat.life();//Cannot resolve method 'life()' } }
這裡就產生了向上轉型,Animal dog= new Dog();Animal cat= new Cat();將子類對象Dog和Cat轉化為父類對象Animal。這個時候Animal這個引用調用的都是子類方法。再去調用子類單獨的方法就會報錯。如果非要調用也不是說不可以,那就要強轉了。既然現在已經是父類了,那就強轉為子類唄。
((Dog) dog).run();
((Cat) cat).life();
這樣也可以調用。但是千萬要註意,不能這樣轉,子類引用不能指向父類對象,Dog dog=(Dog)newAnimal();Cat cat = (Cat)new Animal();這樣是絕對不行的。就好像兒子可以生出爸爸一樣,這樣不和常理。
如果在向上轉型時,子類並沒有重寫父類的方法,那麼調用的就是父類中的方法。
到此為止,也可以證明前面說的一個結論:向上轉型會使子類單獨定義的方法會丟失。
4、向下轉型
與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉為子類對象。這裡我們就會想到,即然子類向上轉型為了父類,而子類又繼承了父類的屬性和方法,為什麼還要將父類轉型為子類。是因為對象的多態性只適用於方法,而不適用於屬性。所以當我們在使用多態的時候,就不能調用子類中的屬性和特有的方法了,所以需要向下轉型。(記憶體中實際上是載入了子類所特有的屬性和方法,但是由於變數聲明的是父類類型,導致在編譯時只能調用父類中聲明的屬性和方法,子類特有的屬性和方法不能調用)
我們還是使用向上轉型那裡的代碼為例(Anima、Dog、Cat類):
public class AnimalTest { public static void main(String[] args) { Animal dog=new Dog();//Dog向上轉型成Animal Dog dog1= (Dog) dog;//向下轉型為Dog dog1.eat(); dog1.skill(); Cat cat=(Cat) dog;//java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat cat.eat(); cat.skill(); } }
運行結果:
我們可以發現向下轉型為Dog沒有報錯,但是轉型為Cat卻報錯了,這個倒不難理解,因為開始向上轉型本來是Dog,然後再變回Dog,總不能Dog變成Cat吧。所以會報類型轉換錯誤。
向下轉型註意事項
- 向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)
- 向下轉型只能轉型為本類對象(貓是不能變成狗的)。
向下轉型我們一般會使用 instanceof 關鍵字來判斷:
使用方法:a instanceof A:判斷對象a是否為對象A的實例,如果是,返回true,如果不是,則返回false。
public class AnimalTest { public static void main(String[] args) { AnimalTest test=new AnimalTest(); test.show(new Animal()); test.show(new Dog()); test.show(new Cat()); } public void show(Animal animal){ if (animal instanceof Dog){ Dog dog= (Dog) animal; dog.eat(); dog.skill(); dog.run(); } } }
運行結果:
我們可以發現測試方法調用三次show方法,分別傳入了Animal、Dog、Cat對象,由於show()方法只判斷了Dog是否是該對象,所以Dog返回了true,輸出了Dog的信息,而其他的返回了false,則沒有輸出然後信息。
5、經典案例
Java的多態和轉型都瞭解以後,現在趁熱打鐵,來點網上多態非常經典的例題:
來源:https://blog.csdn.net/Jian_Yun_Rui/article/details/52937791
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)); } }
運行的結果:
前面3個強行發現還能得到答案,但是從第4個之後就有點頭暈。
我們來慢慢分析第4個:首先是子類B類向上轉型為父類A,而子類B有重寫了父類A中的show(A obj)方法,所以a2變數能調用的只有父類A類中的show(D obj)和子類B中的show(A obj),而B是繼承自A類的,D繼承自B類,所以不可能調用show(D obj)方法,所以結果是4--B and A;剩下的依次類推。
當父類對象變數引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變數類型決定可調用的方法。如果子類中沒有覆蓋該方法,那麼會去父類中尋找。但是它仍然要根據繼承鏈中方法調用的優先順序來確認方法,該優先順序為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
6、總結
通過上面對多態的學習,可以小結一下概念:
1、Java多態也可以用一句話表示:父類的引用指向子類的對象。
2、運行時多態的前提:繼承,重寫,向上轉型。
3、多態能夠減少重覆代碼,使代碼變得簡潔;提高了代碼的擴展性。
4、多態其實是一種虛擬方法調用。歸結為一句話:編譯看左邊,運行看右邊。
5、向上轉型就是是將子類對象轉為父類對象。
6、繼承鏈中對象方法的調用的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。