天天看點

面向對象之多态【向上轉型與向下轉型】

簡單的了解多态

多态,簡而言之就是同一個行為具有多個不同表現形式或形态的能力。比如說,有一杯水,我不知道它是溫的、冰的還是燙的,但是我一摸我就知道了。我摸水杯這個動作,對于不同溫度的水,就會得到不同的結果。這就是多态。

那麼,java中是怎麼展現多态呢?我們來直接看代碼:

運作結果:

這裡的方法showTem()就相當于你去摸水杯。我們定義的water類型的引用變量w就相當于水杯,你在水杯裡放了什麼溫度的水,那麼我摸出來的感覺就是什麼。就像代碼中的那樣,放置不同溫度的水,得到的溫度也就不同,但水杯是同一個。

想必你也看出來了,這段代碼中最關鍵的就是這一句

這句代碼展現的就是向上轉型。後面我會詳細講解這一知識點。

多态的分類

已經簡單的認識了多态了,那麼我們來看一下多态的分類。

多态一般分為兩種:重寫式多态和重載式多态。重寫和重載這兩個知識點前面的文章已經詳細将結果了,這裡就不多說了。

重載式多态,也叫編譯時多态。也就是說這種多态再編譯時已經确定好了。重載大家都知道,方法名相同而參數清單不同的一組方法就是重載。在調用這種重載的方法時,通過傳入不同的參數最後得到不同的結果。

但是這裡是有歧義的,有的人覺得不應該把重載也算作多态。因為很多人對多态的了解是:程式中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在程式設計時并不确定,而是在程式運作期間才确定,這種情況叫做多态。 這個定義中描述的就是我們的第二種多态—重寫式多态。并且,重載式多态并不是面向對象程式設計特有的,而多态卻是面向對象三大特性之一(如果我說的不對,記得告訴我。。)。

我覺得大家也沒有必要在定義上去深究這些,我的了解是:同一個行為具有多個不同表現形式或形态的能力就是多态,是以我認為重載也是一種多态,如果你不同意這種觀點,我也接受。

重寫式多态,也叫運作時多态。這種多态通過動态綁定(dynamic binding)技術來實作,是指在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。也就是說,隻有程式運作起來,你才知道調用的是哪個子類的方法。 

這種多态通過函數的重寫以及向上轉型來實作,我們上面代碼中的例子就是一個完整的重寫式多态。我們接下來講的所有多态都是重寫式多态,因為它才是面向對象程式設計中真正的多态。

動态綁定技術涉及到jvm,暫時不講(因為我也不懂,哈哈哈哈),感興趣的可以自己去研究一下,我暫時還沒有時間去研究jvm。。

多态的條件

前面說過,我們接下來說的多态,都是運作時多态。

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

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

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

繼承也可以替換為實作接口。

繼承和重寫之前都說過了,接下來我們來看一下轉型是什麼。

向上轉型與向下轉型

向上轉型

子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是将子類對象轉為父類對象。此處父類對象可以是接口。

案例驅動

看一個大家都知道的例子:

這就是向上轉型,Animal animal = new Cat();将子類對象Cat轉化為父類對象Animal。這個時候animal這個引用調用的方法是子類方法。

關于方法調用的順序,我們後面會詳細講解。

轉型過程中需要注意的問題

向上轉型時,子類單獨定義的方法會丢失。比如上面Dog類中定義的run方法,當animal引用指向Dog類執行個體時是通路不到run方法的,animal.run()會報錯。

子類引用不能指向父類對象。Cat c = (Cat)new Animal()這樣是不行的。

向上轉型的好處

減少重複代碼,使代碼變得簡潔。

提高系統擴充性。

舉個例子:比如我現在有很多種類的動物,要喂它們吃東西。如果不用向上轉型,那我需要這樣寫:

一種動物寫一個方法,如果我有一萬種動物,我就要寫一萬個方法,寫完大概猴年馬月都過了好幾個了吧。好吧,你很厲害,你耐着性子寫完了,以為可以放松一會了,突然又來了一種新的動物,你是不是又要單獨為它寫一個eat方法?開心了麼?

那如果我使用向上轉型呢?我隻需要這樣寫:

恩,搞定了。代碼是不是簡潔了許多?而且這個時候,如果我又有一種新的動物加進來,我隻需要實作它自己的類,讓他繼承Animal就可以了,而不需要為它單獨寫一個eat方法。是不是提高了擴充性?

向下轉型

與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉為子類對象。(請注意!這裡是有坑的。)

先看一個例子:

為什麼第一段代碼不報錯呢?相比你也知道了,因為a本身就是Cat對象,是以它理所當然的可以向下轉型為Cat,也理所當然的不能轉為Dog,你見過一條狗突然就變成一隻貓這種操蛋現象?

而a1為Animal對象,它也不能被向下轉型為任何子類對象。比如你去考古,發現了一個新生物,知道它是一種動物,但是你不能直接說,啊,它是貓,或者說它是狗。

向下轉型注意事項

向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)

向下轉型隻能轉型為本類對象(貓是不能變成狗的)。

大概你會說,我特麼有病啊,我先向上轉型再向下轉型??

我們回到上面的問題:喂動物吃飯,吃了飯做點什麼呢?不同的動物肯定做不同的事,怎麼做呢?

現在,你懂了麼?這就是向下轉型的簡單應用,可能舉得例子不恰當,但是也可以說明一些問題。

敲黑闆,劃重點!看到那個instanceof了麼?

經典案例分析多态

基本的多态和轉型我們都會了,最後加點餐。看一個經典案例:

前三個,強行分析,還能看得懂。但是第四個,大概你就傻了吧。為什麼不是b and b呢?

這裡就要學點新東西了。

當父類對象引用變量引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。如果子類中沒有覆寫該方法,那麼會去父類中尋找。

可能讀起來比較拗口,我們先來看一個簡單的例子:

Y繼承了X,覆寫了X中的show(Y y)方法,但是沒有覆寫show()方法。

這個時候,引用類型為X的x指向的對象為Y,這個時候,調用的方法由Y決定,會先從Y中尋找。執行x.show(new Y());,該方法在Y中定義了,是以執行的是Y裡面的方法;

但是執行x.show();的時候,有的人會說,Y中沒有這個方法啊?它好像是去父類中找該方法了,因為調用了X中的方法。

事實上,Y類中是有show()方法的,這個方法繼承自X,隻不過沒有覆寫該方法,是以沒有在Y中明确寫出來而已,看起來像是調用了X中的方法,實際上調用的還是Y中的。

這個時候再看上面那句難了解的話就不難了解了吧。X是引用變量類型,它決定哪些方法可以調用;show()和show(Y y)可以調用,而show(int i)不可以調用。Y是被引用對象的類型,它決定了調用誰的方法:調用y的方法。

上面的是一個簡單的知識,它還不足以讓我們了解那個複雜的例子。我們再來看這樣一個知識:

繼承鍊中對象方法的調用的優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

如果你能了解這個調用關系,那麼多态你就掌握了。我們回到那個複雜的例子:

abcd的關系是這樣的:C/D —> B —> A

我們先來分析4 : a2.show(b)

首先,a2是類型為A的引用類型,它指向類型為B的對象。A确定可調用的方法:show(D obj)和show(A obj)。

a2.show(b) ==> this.show(b),這裡this指的是B。

然後.在B類中找show(B obj),找到了,可惜沒用,因為show(B obj)方法不在可調用範圍内,this.show(O)失敗,進入下一級别:super.show(O),super指的是A。

在A 中尋找show(B obj),失敗,因為沒用定義這個方法。進入第三級别:this.show((super)O),this指的是B。

在B中找show((A)O),找到了:show(A obj),選擇調用該方法。

輸出:B and A

如果你能看懂這個過程,并且能分析出其他的情況,那你就真的掌握了。

我們再來看一下9:b.show(d)

首先,b為類型為B的引用對象,指向類型為B的對象。沒有涉及向上轉型,隻會調用本類中的方法。

在B中尋找show(D obj),方法。現在你不會說沒找到了吧?找到了,直接調用該方法。

輸出 A and D。

總結

本篇文章的内容大體上就是這些了。我們來總結一下。

多态,簡而言之就是同一個行為具有多個不同表現形式或形态的能力。

多态的分類:運作時多态和編譯時多态。

運作時多态的前提:繼承(實作),重寫,向上轉型

向上轉型與向下轉型。