java方法的多態性理解


1.什么是java的多態

瀏覽了別人博客中的一些介紹多態的文章,發現大家的描述有點不一樣,主要區別在於是否把方法的重寫算做多態。一種我比較認同的說法如下:

多態分為兩種

a. 編譯時多態:方法的重載;

b. 運行時多態:JAVA運行時系統根據調用該方法的實例的類型來決定選擇調用哪個方法則被稱為運行時多態。(我們平時說得多的事運行時多態,所以多態主要也是指運行時多態);

上述描述認為重載也是多態的一種表現,不過多態主要指運行時多態。

2.運行時多態

a. 面向對象的三大特性:封裝、繼承、多態。從一定角度來看,封裝和繼承幾乎都是為多態而准備的。這是我們最后一個概念,也是最重要的知識點。

b. 多態的定義:指允許不同類的對象對同一消息做出響應。即同一消息可以根據發送對象的不同而采用多種不同的行為方式。(發送消息就是函數調用)

c. 實現多態的技術稱為:動態綁定(dynamic binding),是指在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。

d. 多態的作用:消除類型之間的耦合關系。

e. 現實中,關於多態的例子不勝枚舉。比方說按下 F1 鍵這個動作,如果當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;如果當前在 Word 下彈出的就是 Word 幫助;在 Windows 下彈出的就是 Windows 幫助和支持。同一個事件發生在不同的對象上會產生不同的結果。

下面是多態存在的三個必要條件,要求大家做夢時都能背出來!

多態存在的三個必要條件
一、要有繼承;
二、要有重寫;
三、父類引用指向子類對象。

 多態的好處

1.可替換性(substitutability)。多態對已存在代碼具有可替換性。例如,多態對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。
2.可擴充性(extensibility)。多態對代碼具有可擴充性。增加新的子類不影響已存在類的多態性、繼承性,以及其他特性的運行和操作。實際上新加子類更容易獲得多態功能。例如,在實現了圓錐、半圓錐以及半球體的多態基礎上,很容易增添球體類的多態性。
3.接口性(interface-ability)。多態是超類通過方法簽名,向子類提供了一個共同接口,由子類來完善或者覆蓋它而實現的。如圖8.3 所示。圖中超類Shape規定了兩個實現多態的接口方法,computeArea()以及computeVolume()。子類,如Circle和Sphere為了實現多態,完善或者覆蓋這兩個接口方法。
4.靈活性(flexibility)。它在應用中體現了靈活多樣的操作,提高了使用效率。
5.簡化性(simplicity)。多態簡化對應用軟件的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤為突出和重要。

3.代碼理解

看了上面的描述,我們大概知道以下幾點:

a. 運行時多態是在父類引用指向子類對象時產生的。一個父類的引用可以指向多種子類對象,那么運行時對於同一個消息應該如何做出響應呢?這就由實際的被引用的對象的類型來決定。

b. 為什么要有重寫呢?難道父類中的方法沒有被重寫,直接調用子類中存在的方法難道是不行嗎?看個例子如下:

上面的例子中,當父類中的getName()被注釋掉以后,調用father.getName()方法會出錯。因此,當父類引用指向子類方法時,必須調用那些父類中存在的方法,如果子類中對該方法進行了重寫,那么在運行時就會動態調用子類中的方法,這就是多態。

c. 要有繼承很好理解,沒有繼承的話,哪來的重寫呢。

4.深一點

基本了解了多態以后,我們就可以看明白下面這個例子了,它的輸出結果是什么呢?

答案是"son",結合前面的解釋,我們很容易判斷出來。子類的getName()方法重寫了父類中的方法,在main中,父類的引用father指向了子類的對象son,當我們調用father的getName()方法時,實際上是動態綁定到了子類的getName()上,所以會打印出"son"。

5.再深一點

你是否真正理解了多態呢?請看下面的例子:

上面這個例子中,下面四條語句的輸出結果是什么呢?

a1.show(b);
a1.show(c);
a2.show(b);
a2.show(c);

結果如下:

 

對於前兩條語句的結果我們很容易理解,那第三條和第四條的,為什么結果和我們想的不一樣,不應該是"B and B"嗎?要理解這是為什么,我們要先理解下面這句話:

當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。 (但是如果強制把超類轉換成子類的話,就可以調用子類中新添加而超類沒有的方法了)

看一下標紅的那句話,我們知道問題所在了嗎?

當運行 a2.show(b) 的時候,實際是要調用一個 show(B obj) 的方法,但是 A 中有這樣一個方法嗎?沒有!但是由於 B 繼承自 A,所以會調用 A 中的 show(A obj) 的方法,但是調用時候發現這個方法已經被 B 重寫了,所以就會轉向來調用 B 中的 show(A obj) 方法。所以才會打印出"B and A"。

實際上這里涉及方法調用的優先問題 ,優先級由高到低依次為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。讓我們來看看它是怎么工作的。

比如,a2.show(b),a2是一個引用變量,類型為A,則this為a2,b是B的一個實例,於是它到類A里面找show(B obj)方法,沒有找到,於是到A的super(超類)找,而A沒有超類,因此轉到第三優先級this.show((super)O),this仍然是a2,這里O為B,(super)O即(super)B即A,因此它到類A里面找show(A obj)的方法,類A有這個方法,但是由於a2引用的是類B的一個對象,B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(A obj),輸出為"B and A”。

怎么樣?理解了嗎?

 

問題還要繼續,現在我們再來看上面的分析過程是怎么體現出紅色字體那句話的內涵的。它說:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。還是拿a2.show(b)來說吧。

 

a2是一個引用變量,類型為A,它引用的是B的一個對象,因此這句話的意思是由B來決定調用的是哪個方法。因此應該調用B的show(B obj)從而輸出"B and B”才對。但是為什么跟前面的分析得到的結果不相符呢?!問題在於我們不要忽略了藍色字體的后半部分,那里特別指明:這個被調用的方法必須是在超類中定義過的,也就是被子類覆蓋的方法。B里面的show(B obj)在超類A中有定義嗎?沒有!那就更談不上被覆蓋了。實際上這句話隱藏了一條信息:它仍然是按照方法調用的優先級來確定的。它在類A中找到了show(A obj),如果子類B沒有覆蓋show(A obj)方法,那么它就調用A的show(A obj)(由於B繼承A,雖然沒有覆蓋這個方法,但從超類A那里繼承了這個方法,從某種意義上說,還是由B確定調用的方法,只是方法是在A中實現而已);現在子類B覆蓋了show(A obj),因此它最終鎖定到B的show(A obj)。這就是那句話的意義所在,到這里,我們可以清晰的理解Java的多態性了。

 

6. 最后一個練習!

看下面的例子:

上面例子中的輸出是什么呢?答案是:ai ni

有了前一個例子我們就會很容易理解這個例子。在B類中是沒有對A中的show方法進行重寫,所以當a.show()時調用的是父類中的show方法,父類中show方法調用了show2方法,但是在調用的時候發現show2方法已經被子類重寫,因此會調用子類中show2方法,因此輸出"ai"。可見,當父類引用指向子類對象的時候,對父類中方法的調用都會綁定到子類中重寫后的方法上,如果子類沒有對方法進行重寫,那么會直接調用父類中的方法,相當於是直接調用從父類繼承的方法

還需要注意的一點是子類在重寫父類的方法時,方法的訪問權限不能更低!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM