理解Java多態
面向對象編程有三大特性:封裝、繼承、多態。
封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。
繼承是為了重用父類代碼。兩個類若存在IS-A的關系就可以使用繼承。,同時繼承也為實現多態做了鋪墊。
那麽什麽是多態呢?在面向對象語言中,接口的多種不同的實現方式即為多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成為一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作(摘自“Delphi4 編程技術內幕”)。簡單的說,就是一句話:允許父類型的引用指向子類型的實例對象。因為存在類型轉換機制,從而造成無法在編譯時識別一個引用表達式所指向的實例對象是該引用表達式的類型的實例對象,還是其子類型的實例對象,所以在動態多態性的方法調用中無法在編譯時識別具體調用的成員方法,而這一般需要在運行時才能被Java虛擬機識別,因此稱之為動態多態性。
由此,我們也可以看出,動態多態性離不開繼承(對於接口來說是實現)。在繼承中,我們知道向上轉型,但是向上轉型會導致一些方法和屬性的丟失,我們舉個例子,定義一個父類Animal,子類Cat。Animal有call的方法,Cat中重寫了call這個方法,Cat有屬於自己的scratch行為,代碼如下:
class Animal { public void call(){ System.out.println("動物叫"); } } class Cat extends Animal{ public void call(){ System.out.println("動物貓叫"); } public void scratch(){ System.out.println("動物貓撓人"); } } public class AnimalTest { public static void main(String[] args) { // TODO Auto-generated method stub Animal a = new Cat(); a.call(); // a.scratch(); 錯誤,因為Cat()的向上轉換,造成只屬於Cat()中方法的丟失。} }
運行結果:
動物貓叫
我們這樣理解:
- 這裏定義了一個Animal類型的a,它指向Cat對象實例,由於Cat繼承於Animal,所以Cat可以自動向上轉型為Animal,所以a是可以指向Cat實例對象的。
- 由於Cat中重寫了父類Animal的call方法,程序運行時Java虛擬機識別出a指向的實例對象是Cat,因此a.call()實際調用的是Cat中的call()方法。
- 由於Animal中不存在scratch方法,所以子類Cat在向上轉換時會丟失scratch方法(“丟失”我覺得並不準確,實際上在實例化Cat後,scratch方法是一直存在的,只不過向上轉型後對於Animal的引用來說scratch隱藏了起來),因此a.scratch是有誤的。
實現多態有三個必要條件:繼承、重寫、向上轉型,向上轉型造成了子類中一些方法的丟失。 接下來,我們看個經典的例子:
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
上面程序中我們可以看出A、B、C、D存在如下關系
我們把靜態多態性看做是不同的方法,類A有兩種方法,表示為A:show(D);A:show(A);
類B繼承於A,所以類B繼承了類A的兩種方法,表示為A:show(D);A:show(A);同時,類B有自己的方法,表示為B:show(B);B:show(A);這裏B中重寫了類A的show(A)方法,因此,B中的方法最終為A:show(D);B:show(B);B:show(A);
類C,類D繼承於B,所以類C、類D的方法為:A:show(D);B:show(B);B:show(A);
接下來,我們分析變量:
A a1 = new A(); a1擁有類A的兩種方法。A:show(D);A:show(A);
A a2 = new B(); a2指向實例對象B,因此a2有類B的三種方法,又因為類B的向上轉換,導致只保留類B中與類A中相同的方法,因此a2中的方法為A:show(D);B:show(A);
B b = new B(); b擁有類B的三種方法A:show(D);B:show(B);B:show(A);
C c = new C(); c擁有類C的三種方法A:show(D);B:show(B);B:show(A);
D d = new D(); d擁有類D的三種方法A:show(D);B:show(B);B:show(A);
接著,我們分析方法:
a1.show(b):b是類型B的引用,a1不含有參數為類型B的方法,b可以向上轉型為A,調用A:show(A);
a1.show(c)同上;
a1.show(d):d是類型D的引用,a1中有方法show(D),可以直接調用A:show(D)
a2.show(b):b是類型B的引用,a2中不含有參數為類型B的方,b可以向上轉型為A,調用B:show(A);
a2.show(c)同上
a2.show(d):a2中有方法show(D),可以直接調用A:show(d)
b.show(b):b中有方法show(B),可以直接調用B:show(B);
b.show(c):c是類型為C的引用,b中沒有方法show(C),但是c可以向上轉型為B,調用show(B);轉型的時候,先轉為直接父類,若沒有找到直接父類對應的參數,再繼續向上轉;
b.show(d):b中有方法show(D),可以直接調用A:show(D);
後記:以上是本人對多態繼承的一些理解,剛開始接觸Java,若有不對,懇請指正,虛心學習。
參考:
http://www.cnblogs.com/chenssy/p/3372798.html
Java程序設計教程
https://baike.baidu.com/item/%E5%A4%9A%E6%80%81/2282489?fr=aladdin
理解Java多態