不少人老是喜歡,或者說錯誤地將JAVA中的多態理解得很複雜,最多見的錯誤說法就是所謂「方法的多態」,他們會給出相似下面的例子來佐證「多態是指方法的多態」:java
//Enginner和Mechanic是Employee的子類,構造函數參數均爲月薪salary Employee a=new Enginner(100); Employee b=new Mechanic(100); //getAnnualSalary是Employee類的方法,用於計算並返回年薪 System.out.println(a.getAnnualSalary());//輸出1500,Enginner年薪爲15倍月薪 System.out.println(b.getAnnualSalary());//輸出1300,Mechanic年薪爲13倍月薪
從結果上看,a、b都是Employee類對象變量,然而對a調用getAnnualSalary()返回的是15*salary,對b調用getAnnualSalary()卻返回了13*salary,好像的確是所謂「方法的多態」,畢竟對同一類對象變量調用同一個方法,內部實現方式卻出現不一樣了嘛。基於這樣的想法,甚至有一些人將多態擴展到了更普遍、更復雜的狀況,好比下面這種,連泛型都算進了多態中:數組
那麼,多態真的是有那麼多種狀況嗎?真的是隻要方法名相同,而參數或者內部實現方式不一樣,就要當作是多態嗎?不不不,這種說法純屬扯淡,JAVA中的多態有且只有一種狀況:對象變量是多態的。這個理解相當重要,能夠說對於多態的概念,要記住的就是這個點。可是,爲何在上面的例子中,對a和b調用同一個方法,會有不一樣的效果呢?注意,這是方法調用的知識範疇,只是剛好和多態相關罷了。下面咱們就來理清一下多態與方法調用的知識。函數
JAVA中的多態是由繼承機制帶來的,正是由於有繼承機制,因此才存在多態。簡單來講,多態的原由就是JAVA中容許一個父類對象變量引用一個子類對象(至於爲何咱們以後會說):學習
//Son是Father的子類 Father variable=new Son(); //variable是一個Father類對象變量,但它實際引用的對象倒是Son類對象
因爲父類對象變量能夠引用子類對象,因此當咱們看到一個A類對象變量時,咱們不能一口咬定它所引用的對象就是A類對象,它也有可能引用B類對象、C類對象……只要它引用的對象是A類的子類對象就行。這就是多態:對象變量實際引用的對象的類型不必定是對象變量聲明的類型。spa
可是單純的多態並沒卵用,我令Employee類對象變量a引用了一個Enginner對象,而後呢,即使我在Enginner中重寫了getAnnualSalary以返回15薪,在對a調用getAnnualSalary時依然返回12薪嗎?(假設Employee類中getAnnualSalary返回12*salary)那有什麼意義?翻譯
因此實際上,多態的存在,必需要有方法調用時的動態綁定支持纔有意義。所謂方法調用的動態綁定,就是:虛擬機會調用與變量所引用的實際類型最匹配的那個方法。code
舉例來講,Employee類的getAnnualSalary返回12*salary,Enginner類重寫了該方法以返回15*salary,那麼當出現下述狀況時:對象
Employee a=new Enginner(100); int annualSalary=a.getAnnualSalary();
虛擬機會先判斷變量a所引用的對象其實是什麼類型(此例實際類型爲Enginner),而後查看其實際類型是否重寫了該方法(此例Enginner重寫了Employee中的getAnnualSalary方法),若是是則調用其實際類型中的該方法(此例也即調用Enginner類中返回15*salary的getAnnualSalary),不然調用a聲明的類型(即Employee)中的該方法。blog
經過多態+動態綁定,咱們就能夠快速地實現一些效果。好比說寫一個抽象類List,聲明一個get方法以獲取列表中指定元素,聲明一個set方法以設置列表中指定元素,而後實現一個非抽象子類LinkedList,內部採用鏈表結構存儲列表,再實現一個ArrayList,內部採用數組結構存儲列表。這樣一來,咱們就能夠利用多態+動態綁定這樣寫代碼:繼承
List a=new ArrayList(); oldValue=a.get(i); a.set(i,newValue);
若是咱們想要使用一個能夠良好支持隨機訪問的列表,咱們就能夠像上面這樣寫,即令a引用一個ArrayList對象,若是哪一天咱們但願此處改用使用良好支持動態增減的列表了,只須要將
List a=new ArrayList();
改成:
List a=new LinkedList();
便可,而其他代碼不須要改動。經過方法的動態綁定,對get和set的調用都將自動成爲對LinkedList類中的方法調用。這樣一來,改變列表的實際存儲結構就成了一個很簡單的事情。
此外,多態+動態綁定還能夠在「只關注通用方法」時起到簡化代碼的效果。什麼意思呢?舉例來講就是Enginner和Mechanic有各自不一樣的,在Employee類基礎上新增的方法。可是咱們在統計員工薪水時,並不想關注它們各自獨有的東西,只想關注一樣做爲Employee都會有的年薪。那我就能夠將各個Enginner、Mechanic都放進一個Employee數組中,而後遍歷該數組,對每一個元素調用getAnnualSalary並輸出,而不用爲Enginner創個數組遍歷一遍,再對Mechanic創個數組遍歷一遍。
固然,多態+動態綁定還有許多其餘用途,尤爲是在JAVA的各集合類應用上,此處不予細談。
若是說動態綁定是解決了多態的方法調用問題,那麼靜態綁定就是爲了快速實現(方法)重載機制。所謂重載機制就是指在JAVA中,容許一個方法的名字與已存在的另外一個方法相同,只要這兩個方法的參數個數或類型不一樣便可。這種多個方法名字相同、參數不一樣的狀況,就是方法重載。此處所說的「方法」也能夠是構造器,所以這種機制叫作:重載。
要想實現重載,就得在調用方法時,根據調用時所給的參數決定到底調用哪一個方法。可是到底該何時肯定這件事呢?在JAVA中,這個確認步驟在編譯器將源代碼翻譯爲字節碼時肯定,也即由編譯器javac根據方法調用時所給的參數個數、類型來肯定實際該調用哪一個方法,從而實現重載。由於是在編譯時肯定的,因此這個綁定過程就是靜態綁定。
可是須要注意的是,靜態綁定並不算真正的「綁定」,它實際上是一個篩選。什麼意思呢?舉例來講,假設Employee類的getAnnualSalary還有一個帶參數的版本:getAnnualSalary(double bonusRate),即給定一個「獎金比例」來計算年薪,那麼當對一個Employee類對象變量a調用getAnnualSalary()時,編譯器會先進行靜態綁定,即篩選,從而肯定此處的方法調用不多是帶參數的版本,但有多是Employee類的該方法,也有多是Enginner或Mechanic類的該方法,通過靜態綁定後,剩下了三種可能,再由虛擬機在運行時經過動態綁定肯定真正調用的方法。
其實重載也能夠作成讓虛擬機來作的事情,可是經過編譯器的靜態綁定篩選掉一部分方法,就能夠令虛擬機在肯定實際調用方法時減小一些工做量,只關注於動態綁定的可能方法上。因此說靜態綁定是爲了快速實現重載。
有關多態、方法調用的相關知識固然還有許多細節,好比一個方法x(int)和重載的方法x(double),在調用x(3)時既能夠是調用x(int),也能夠是調用x(double),到底選哪一個?爲何重載不容許僅僅返回類型不一樣?不過這些細節問題並非本文想要討論的東西,本文要說的基本上就是上面那些提綱挈領的內容。
總的來講,在學習JAVA多態時最重要的點就是要明白多態就是指對象變量的多態,不要去把多態這個概念複雜化。至於所謂「方法的多態」,其實就是方法調用的靜態綁定(篩選)和動態綁定。