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類的子類對象就行。這就是多態:對象變量實際引用的對象的類型不一定是對象變量聲明的類型。

  但是單純的多態並沒卵用,我令Employee類對象變量a引用了一個Enginner對象,然後呢,即便我在Enginner中重寫了getAnnualSalary以返回15薪,在對a調用getAnnualSalary時依然返回12薪嗎?(假設Employee類中getAnnualSalary返回12*salary)那有什麼意義?

  所以實際上,多態的存在,必須要有方法調用時的動態綁定支持纔有意義。所謂方法調用的動態綁定,就是:虛擬機會調用與變量所引用的實際類型最匹配的那個方法。

  舉例來說,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)中的該方法。

 

  通過多態+動態綁定,我們就可以快速地實現一些效果。比如說寫一個抽象類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多態時最重要的點就是要明白多態就是指對象變量的多態,不要去把多態這個概念複雜化。至於所謂“方法的多態”,其實就是方法調用的靜態綁定(篩選)和動態綁定。

https://www.cnblogs.com/mm93/p/8620470.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章