天天看點

Java中的多态

  面向對象語言的三大特性:封裝、繼承、多态,而在這三種特性中,多态又是那個極具意義的一個,從某個方面來說,一個OOP語言最核心的特征就是多态,封裝繼承在很多方面都是為了實作多态而出現的。

  而多态又可以分為兩種:

  1、編譯時多态(靜态多态):在編譯的時候就知道要調用的方法,如重載

  2、運作時多态(動态多态,又稱動态綁定):在運作的時候才知道要調用哪個方法

  重載是在我們單獨拎出來的一個定義,而我們常說的多态一般指運作時的多态,也就是說我們要等最後運作的時候才會确定要調用的方法,這與Java虛拟機的運作機制有關,是以多态方法又稱延遲方法。

  多态的實作方法有兩種:

  1、子類繼承父類:子類繼承父類并重寫了父類的方法

  2、實作接口方法:實作接口的類實作了接口的方法

其實這兩種方法有共同點:就是實作了父類或接口的方法,而多态最核心的就是這些被重寫或者被實作方法在子類中的不同表現形式。

  講多态的實作方法又必須知道的一個概念:向上轉型 看一個例子:

  這裡我們有一個基類animal,Dog類和Cat類繼承了animal類并且Override其speak方法,這裡我們可以稱Dog和Cat是animal的子類(導出類),注意此時的mian函數的三個定義方法,第一個是正常的new了一個animal對象,而第4行和第5行就是向上轉型,可以思考一下輸出是什麼

Java中的多态
Java中的多态

View Code

  神奇的事情發生了,我們的引用變量明明定義的animal類,而實際執行的方法卻是對應子類的方法,這樣JVM的機制有關,具體可以見這篇文章:

  https://www.cnblogs.com/kaleidoscope/p/9790766.html

  而這就是我們所要介紹的向上轉型,左邊定義的基類的類别,而實際執行的是右邊子類的方法,向上轉型就是這樣一個特點

  對于:

  animal a2 = new Dog();

  左邊是編譯類型,其編譯類型為animal,就是告訴編譯器我定義了一個annimal類型的引用變量,我将要指向一個對象

  右邊是運作類型,其運作類型是Dog,這就是我們上面說的動态綁定機制,隻在真正運作的時候去找具體實作的方法,而現在找到了運作類型是Dog,是以執行Dog.speak

那再總結一下發生了什麼:

1、Dog繼承了animal

2、Dog重寫了animal的speak方法

3、在main函數裡出現了向上轉型(也就是一個基類的引用指向了一個子類的對象)

  這樣幾個條件,多态發生了,也正是多态機制使一個基類的引用指向了一個子類的對象的方式讓編譯器承認了

同樣這裡需要注意第二個Dog重寫了speak方法,那萬一speak是靜态方法呢?會出現什麼情況?

如果你去思考運作結果就掉入了思維陷阱,靜态方法是不能被重寫了,靜态代表它隻有一份,不允許除它之外的對象去重寫,是以speak是靜态方法,嘗試重寫一定會報錯。

那再思考一個問題:如果沒有重寫方法會怎麼樣?看如下代碼:

  可以看到,我們把子類裡面的speak方法都删除了,這就與繼承的特性相關了,思考一下結果是什麼?

Java中的多态
Java中的多态

  這裡發生的事情就是,我們的運作類型是Dog或者Cat,運作時首先去這兩個子類裡去找speak方法,如果發現找不到,就會向上去找,到父類是animal去找,發現找到了就會直接執行,這個向上的過程是可以無限向上了,直到找到或者到Object類才會結束。

那我們再思考一個問題,我們前面讨論的問題中子類調用的是重寫父類的方法,那向上轉型後的引用變量能調用子類特有的方法麼?如:

  我們可以看到我們用向上轉型的引用變量是無法調用Dog和Cat類特有的方法eat,也就是第五行和第六行會報錯,我們看一下它錯誤的原因:

Java中的多态

 我們驚訝的發現,編譯器說無法到animal中找到eat方法,我們前面說過,對于一個:

  animal a2 = new Dog(); 

  這樣定義的引用變量a2,其編譯類型是左邊的animal,其運作類型是右邊的Dog,從上面的結果來看,編譯器先确定了我們總共的方法有哪些,就是從編譯類型中确定了方法的多少,也就是運作類型中特有的方法編譯器是找不到的。

  那我們可以下一個結論:向上轉型的引用變量能調用的方法的數量(種類)是由編譯類型(左邊的)決定的,而到具體執行,先看子類有沒有重寫,如果重寫就調用子類,沒有就往上追溯,也就是實際方法的實作調用順序是從右邊的運作類型開始,找到就調用,沒找到就往上(父類)去找這個方法。

這就是向上轉型的全貌,那可能想問?這有什麼意義麼?我引用它處的一段,真正還得自己使用了去體會,如下:

1.可替換性(substitutability)。多态對已存在代碼具有可替換性。例如,多态對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。

2.可擴充性(extensibility)。多态對代碼具有可擴充性。增加新的子類不影響已存在類的多态性、繼承性,以及其他特性的運作和操作。實際上新加子類更容易獲得多态功能。例如,在實作了圓錐、半圓錐以及半球體的多态基礎上,很容易增添球體類的多态性。

3.接口性(interface-ability)。多态是超類通過方法簽名,向子類提供了一個共同接口,由子類來完善或者覆寫它而實作的。如圖8.3

所示。圖中超類Shape規定了兩個實作多态的接口方法,computeArea()以及computeVolume()。子類,如Circle和Sphere為了實作多态,完善或者覆寫這兩個接口方法。

4.靈活性(flexibility)。它在應用中展現了靈活多樣的操作,提高了使用效率。

5.簡化性(simplicity)。多态簡化對應用軟體的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤為突出和重要。

  上面說完了向上轉型,其中的一個特點就是引用變量能調用方法的種類是由編譯器決定的,我們無法調用子類特有的方法,那當我們真的想調用子類中特有的方法都時候,就可以采用向下轉型的方式,如:

  觀察第四行和第五行就可以發現,我們将a2的編譯類型轉成了Dog并賦給了a4,a4成功調用了Dog的獨有方法eat(),而a4調用完eat後,同樣也可以将編譯類型重轉為animal,這樣a4就是一個向上轉型後的引用變量,編譯類型變成了animal,運作類型還是Dog。

  可以觀察出一個規律,我們用類型轉換的時候隻能修改它的編譯類型,而運作類型早在定義的時候就已經固定了(本質上是位址已經固定)。 

11