面向對象三大特徵:封裝、繼承、多態。多態是Java面向對象最核心,最難以理解的內容。從必定角度來看,封裝和繼承幾乎都是爲多態而準備的。java
多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編譯時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。學習
好比有一種動物,藏在某個看不見得地方,你知道它是一種動物,可是不知道具體是哪一種動物,只有等它發出叫聲才能辨別,是貓——喵喵喵,是狗——旺旺旺,你一聽就知道這是什麼動物,對於不一樣的動物會有不一樣的結果,能夠理解爲多態。測試
Java多態也能夠用一句話表示:父類的引用指向子類的對象。ui
多態存在的三個必要條件this
一、繼承:在多態中必須存在有繼承關係的子類和父類。spa
二、重寫:子類對父類中某些方法進行從新定義,在調用這些方法時就會調用子類的方法。.net
三、向上轉型:在多態中須要將子類的引用賦給父類對象,只有這樣該引用纔可以具有技能調用父類的方法和子類的方法。code
開頭說了這麼多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(); } }
運行結果:blog
從上面的測試類來看,咱們都是建立不一樣子類的對象,相同的父類引用,卻表現出它們不一樣的特徵,這就是體現了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(); } }
從這裏能夠看出來多態的優勢:
一、減小重複代碼,使代碼變得簡潔(由繼承保證)。
二、提升了代碼的擴展性(由多態保證)。
可是也有缺點:子類單獨定義的方法會丟失。後面的向上轉型會介紹到。
多態實際上是一種虛擬方法調用。在編譯期間,只能調用父類中聲明的方法,可是在運行期間,實際執行的是子類重寫父類的方法。
總結爲一句話:編譯看左邊,運行看右邊(可能看到這句話會有點頭暈,可是理解下面向上轉型的概念就應該可以理解這句話了)。
子類引用的對象轉換爲父類類型稱爲向上轉型。通俗地說就是是將子類對象轉爲父類對象。此處父類對象也能夠是接口。
咱們用前面多態的例子舉例,只是在子類中添加了它們本身的方法,父類中沒有定義,以下。
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();這樣是絕對不行的。就好像兒子能夠生出爸爸同樣,這樣不和常理。
若是在向上轉型時,子類並無重寫父類的方法,那麼調用的就是父類中的方法。
到此爲止,也能夠證實前面說的一個結論:向上轉型會使子類單獨定義的方法會丟失。
與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉爲子類對象。這裏咱們就會想到,即然子類向上轉型爲了父類,而子類又繼承了父類的屬性和方法,爲何還要將父類轉型爲子類。是由於對象的多態性只適用於方法,而不適用於屬性。因此當咱們在使用多態的時候,就不能調用子類中的屬性和特有的方法了,因此須要向下轉型。(內存中其實是加載了子類所特有的屬性和方法,可是因爲變量聲明的是父類類型,致使在編譯時只能調用父類中聲明的屬性和方法,子類特有的屬性和方法不能調用)
咱們仍是使用向上轉型那裏的代碼爲例(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,則沒有輸出而後信息。
Java的多態和轉型都瞭解之後,如今趁熱打鐵,來點網上多態很是經典的例題:
來源:http://www.javashuo.com/article/p-qfgsjwub-hv.html
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)。
經過上面對多態的學習,能夠小結一下概念:
一、Java多態也能夠用一句話表示:父類的引用指向子類的對象。
二、運行時多態的前提:繼承,重寫,向上轉型。
三、多態可以減小重複代碼,使代碼變得簡潔;提升了代碼的擴展性。
四、多態實際上是一種虛擬方法調用。歸結爲一句話:編譯看左邊,運行看右邊。
五、向上轉型就是是將子類對象轉爲父類對象。
六、繼承鏈中對象方法的調用的優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。