天天看點

【Java學習筆記之二十四】對Java多态性的一點了解

面向對象程式設計有三大特性:封裝、繼承、多态。

      封裝隐藏了類的内部實作機制,可以在不影響使用的情況下改變類的内部結構,同時也保護了資料。對外界而已它的内部細節是隐藏的,暴露給外界的隻是它的通路方法。

      繼承是為了重用父類代碼。兩個類若存在IS-A的關系就可以使用繼承。,同時繼承也為實作多态做了鋪墊。那麼什麼是多态呢?多态的實作機制又是什麼?請看我一一為你揭開:

      所謂多态就是指程式中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在程式設計時并不确定,而是在程式運作期間才确定,即一個引用變量倒底會指向哪個類的執行個體對象,該引用變量發出的方法調用到底是哪個類中實作的方法,必須在由程式運作期間才能決定。因為在程式運作時才确定具體的類,這樣,不用修改源程式代碼,就可以讓引用變量綁定到各種不同的類實作上,進而導緻該引用調用的具體方法随之改變,即不修改程式代碼就可以改變程式運作時所綁定的具體代碼,讓程式可以選擇多個運作狀态,這就是多态性。

      比如你是一個酒神,對酒情有獨鐘。某日回家發現桌上有幾個杯子裡面都裝了白酒,從外面看我們是不可能知道這是些什麼酒,隻有喝了之後才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這裡我們可以描述成如下:

      酒 a = 劍南春

      酒 b = 五糧液

      酒 c = 酒鬼酒

      …

      這裡所表現的的就是多态。劍南春、五糧液、酒鬼酒都是酒的子類,我們隻是通過酒這一個父類就能夠引用不同的子類,這就是多态——我們隻有在運作的時候才會知道引用變量所指向的具體執行個體對象。

      誠然,要了解多态我們就必須要明白什麼是“向上轉型”。在繼承中我們簡單介紹了向上轉型,這裡就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:

      JNC a = new  JNC();

      對于這個代碼我們非常容易了解無非就是執行個體化了一個劍南春的對象嘛!但是這樣呢?

      Wine a = new JNC();

      在這裡我們這樣了解,這裡定義了一個Wine 類型的a,它指向JNC對象執行個體。由于JNC是繼承與Wine,是以JNC可以自動向上轉型為Wine,是以a是可以指向JNC執行個體對象的。這樣做存在一個非常大的好處,在繼承中我們知道子類是父類的擴充,它可以提供比父類更加強大的功能,如果我們定義了一個指向子類的父類引用類型,那麼它除了能夠引用父類的共性外,還可以使用子類強大的功能。

      但是向上轉型存在一些缺憾,那就是它必定會導緻一些方法和屬性的丢失,而導緻我們不能夠擷取它們。是以父類類型的引用可以調用父類中定義的所有屬性和方法,對于隻存在與子類中的方法和屬性它就望塵莫及了---1。

從程式的運作結果中我們發現,a.fun1()首先是運作父類Wine中的fun1().然後再運作子類JNC中的fun2()。

      分析:在這個程式中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),而且重載後的fun1(String a)與 fun1()不是同一個方法,由于父類中沒有該方法,向上轉型後會丢失該方法,是以執行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那麼指向JNC的Wine引用會調用JNC中fun2()方法。

      是以對于多态我們可以總結如下:

      指向子類的父類引用由于向上轉型了,它隻能通路父類中擁有的方法和屬性,而對于子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動态連接配接、動态調用)。

      對于面向對象而已,多态分為編譯時多态和運作時多态。其中編輯時多态是靜态的,主要是指方法的重載,它是根據參數清單的不同來區分不同的函數,通過編輯之後會變成兩個不同的函數,在運作時談不上多态。而運作時多态是動态的,它是通過動态綁定來實作的,也就是我們所說的多态性。

      2.1實作條件

      在剛剛開始就提到了繼承在為多态的實作做了準備。子類Child繼承父類Father,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類Father對象,也可以處理子類Child對象,當相同的消息發送給子類或者父類對象時,該對象就會根據自己所屬的引用而執行不同的行為,這就是多态。即多态性就是相同的消息使得不同的類做出不同的響應。

      Java實作多态有三個必要條件:繼承、重寫、向上轉型。

         繼承:在多态中必須存在有繼承關系的子類和父類。

         重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。

         向上轉型:在多态中需要将子類的引用賦給父類對象,隻有這樣該引用才能夠具備技能調用父類的方法和子類的方法。

         隻有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統一的邏輯實作代碼處理不同的對象,進而達到執行不同的行為。

      對于Java而言,它多态的實作機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆寫的方法。

      2.2實作形式

      在Java中有兩種形式可以實作多态。繼承和接口。

      2.2.1、基于繼承實作的多态

      基于繼承的實作機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行為。

  在上面的代碼中JNC、JGJ繼承Wine,并且重寫了drink()、toString()方法,程式運作結果是調用子類中方法,輸出JNC、JGJ的名稱,這就是多态的表現。不同的對象可以執行相同的行為,但是他們都需要通過自己的實作方式來執行,這就要得益于向上轉型了。

      我們都知道所有的類都繼承自超類Object,toString()方法也是Object中方法,當我們這樣寫時:

     輸出的結果是Wine : JGJ。

      Object、Wine、JGJ三者繼承鍊關系是:JGJ—>Wine—>Object。是以我們可以這樣說:當子類重寫父類的方法被調用時,隻有對象繼承鍊中的最末端的方法才會被調用。但是注意如果這樣寫:

輸出的結果應該是Null,因為JGJ并不存在于該對象繼承鍊中。

      是以基于繼承實作的多态可以總結如下:對于引用子類的父類類型,在處理該引用時,它适用于繼承該父類的所有子類,子類對象的不同,對方法的實作也就不同,執行相同動作産生的行為也就不同。

      如果父類是抽象類,那麼子類必須要實作父類中所有的抽象方法,這樣該父類所有的子類一定存在統一的對外接口,但其内部的具體實作可以各異。這樣我們就可以使用頂層類提供的統一接口來處理該層次的方法。

      2.2.2、基于接口實作的多态

      繼承是通過重寫父類的同一方法的幾個不同子類來展現的,那麼就可就是通過實作接口并覆寫接口中同一方法的幾不同的類展現的。

      在接口的多态中,指向接口的引用必須是指定這實作了該接口的一個類的執行個體程式,在運作時,根據對象引用的實際類型來執行對應的方法。

      繼承都是單繼承,隻能為一組相關的類提供一緻的服務接口。但是接口可以是多繼承多實作,它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一緻的服務接口。是以它相對于繼承來說有更好的靈活性。

      通過上面的講述,可以說是對多态有了一定的了解。現在趁熱打鐵,看一個執行個體。該執行個體是有關多态的經典例子

  運作結果:

      在這裡看結果1、2、3還好了解,從4開始就開始糊塗了,對于4來說為什麼輸出不是“B and B”呢?

      首先我們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆寫的方法。這句話對多态進行了一個概括。其實在繼承鍊中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

      分析:

     從上面的程式中我們可以看出A、B、C、D存在如下關系。

【Java學習筆記之二十四】對Java多态性的一點了解

      首先我們分析5,a2.show(c),a2是A類型的引用變量,是以this就代表了A,a2.show(c),它在A類中找發現沒有找到,于是到A的超類中找(super),由于A沒有超類(Object除外),是以跳到第三級,也就是this.show((super)O),C的超類有B、A,是以(super)O為B、A,this同樣是A,這裡在A中找到了show(A obj),同時由于a2是B類的一個引用且B類重寫了show(A obj),是以最終會調用子類B類的show(A obj)方法,結果也就是B and A。

      按照同樣的方法我也可以确認其他的答案。

      方法已經找到了但是我們這裡還是存在一點疑問,我們還是來看這句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆寫的方法。這我們用一個例子來說明這句話所代表的含義:a2.show(b);

      這裡a2是引用變量,為A類型,它引用的是B對象,是以按照上面那句話的意思是說有B來決定調用誰的方法,是以a2.show(b)應該要調用B中的show(B obj),産生的結果應該是“B and B”,但是為什麼會與前面的運作結果産生差異呢?這裡我們忽略了後面那句話“但是這兒被調用的方法必須是在超類中定義過的”,那麼show(B obj)在A類中存在嗎?根本就不存在!是以這句話在這裡不适用?那麼難道是這句話錯誤了?非也!其實這句話還隐含這這句話:它仍然要按照繼承鍊中調用方法的優先級來确認。是以它才會在A類中找到show(A obj),同時由于B重寫了該方法是以才會調用B類中的方法,否則就會調用A類中的方法。

      是以多态機制遵循的原則概括為:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆寫的方法,但是它仍然要根據繼承鍊中方法調用的優先級來确認方法,該優先級為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。