Java零基礎(chǔ)快速入門|多態(tài)

本篇文章主要內(nèi)容
多態(tài)基礎(chǔ)語(yǔ)法
多態(tài)在開發(fā)中的作用
難點(diǎn)解惑
多態(tài)基礎(chǔ)語(yǔ)法
多態(tài)(Polymorphism)屬于面向?qū)ο笕筇卣髦唬那疤崾欠庋b形成獨(dú)立體,獨(dú)立體之間存在繼承關(guān)系,從而產(chǎn)生多態(tài)機(jī)制。多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力?,F(xiàn)實(shí)中,比如我們按下 F1?鍵這個(gè)動(dòng)作:
如果當(dāng)前在 Flash?界面下彈出的就是 AS?3?的幫助文檔;
如果當(dāng)前在 Word?下彈出的就是 Word?幫助;
如果當(dāng)前在 Windows?下彈出的就是 Windows?幫助和支持。
多態(tài)就是“同一個(gè)行為”發(fā)生在“不同的對(duì)象上”會(huì)產(chǎn)生不同的效果。那么在Java 中多態(tài)是如何體現(xiàn)的呢?
在?Java 中允許這樣的兩種語(yǔ)法出現(xiàn),一種是向上轉(zhuǎn)型(Upcasting),一種是向下轉(zhuǎn)型
(Downcasting),向上轉(zhuǎn)型是指子類型轉(zhuǎn)換為父類型,又被稱為自動(dòng)類型轉(zhuǎn)換,向下轉(zhuǎn)型是指父類型轉(zhuǎn)換為子類型,又被稱為強(qiáng)制類型轉(zhuǎn)換。請(qǐng)看下圖:
?

在Java 語(yǔ)言中有這樣的一個(gè)規(guī)定,無(wú)論是向上轉(zhuǎn)型還是向下轉(zhuǎn)型,兩種類型之間必須要有繼承關(guān)系,沒有繼承關(guān)系情況下進(jìn)行向上轉(zhuǎn)型或向下轉(zhuǎn)型的時(shí)候編譯器都會(huì)報(bào)錯(cuò),這一點(diǎn)要死記硬背哦!
接下來我們來看一段代碼:



運(yùn)行結(jié)果如下圖所示:

其實(shí)在Java 中還允許這樣寫代碼,請(qǐng)看:

運(yùn)行結(jié)果如下圖所示:

以上程序演示的就是多態(tài),多態(tài)就是“同一個(gè)行為(move)”作用在“不同的對(duì)象上”會(huì)有不同的表現(xiàn)結(jié)果。java 中之所以有多態(tài)機(jī)制,是因?yàn)?/span>java 允許一個(gè)父類型的引用指向一個(gè)子類型的對(duì)象。也就是說允許這種寫法:Animal a2?= new Bird(),因?yàn)?Bird is a Animal 是能夠說通的。其中 Animal a1?= new Cat()或者Animal a2?= new Bird()都是父類型引用指向了子類型對(duì)象,都屬于向上轉(zhuǎn)型(Upcasting),或者叫做自動(dòng)類型轉(zhuǎn)換。
我來解釋一下這段代碼片段【Animal a1?= new Cat();a1.move(); 】:java 程序包括編譯和運(yùn)行兩個(gè)階段,分析 java 程序一定要先分析編譯階段,然后再分析運(yùn)行階段,在編譯階段編譯器只知道a1?變量的數(shù)據(jù)類型是Animal,那么此時(shí)編譯器會(huì)去Animal.class?字節(jié)碼中查找move()方法,發(fā)現(xiàn) Animal.class?字節(jié)碼中存在 move()方法,然后將該 move()方法綁定到 a1 引用上, 編譯通過了,這個(gè)過程我們可以理解為“靜態(tài)綁定”階段完成了。緊接著程序開始運(yùn)行,進(jìn)入運(yùn)行階段,在運(yùn)行的時(shí)候?qū)嶋H上在堆內(nèi)存中new 的對(duì)象是Cat?類型,也就是說真正在 move 移動(dòng)的時(shí)候,是 Cat?貓對(duì)象在移動(dòng),所以運(yùn)行的時(shí)候就會(huì)自動(dòng)執(zhí)行 Cat?類當(dāng)中的move()方法,這個(gè)過程可以稱為“動(dòng)態(tài)綁定”。但無(wú)論是什么時(shí)候,必須先“靜態(tài)綁定”成功之后才能進(jìn)入“動(dòng)態(tài)綁定”階段。
來看以下的一段代碼以及編譯結(jié)果:

編譯結(jié)果:

有人認(rèn)為 Cat 貓是可以抓老鼠的呀,為什么會(huì)編譯報(bào)錯(cuò)呢?那是因?yàn)椤?/span>Animal a?= new Cat();”在編譯的時(shí)候,編譯器只知道 a 變量的數(shù)據(jù)類型是 Animal,也就是說它只會(huì)去Animal.class 字節(jié)碼中查找 catchMouse()方法,結(jié)果沒找到,自然“靜態(tài)綁定”就失敗了,編譯沒有通過。就像以上描述的錯(cuò)誤信息一樣:在類型為 Animal 的變量 a 中找不到方法catchMouse()。
那么,假如說我就是想讓這只貓去抓老鼠,以上代碼應(yīng)該如何修改呢?請(qǐng)看以下代碼:

運(yùn)行結(jié)果如下圖所示:

我們可以看到直接使用a?引用是無(wú)法調(diào)用catchMouse()方法的,因?yàn)檫@個(gè)方法屬于子類 Cat 中特有的行為,不是所有 Animal 動(dòng)物都可以抓老鼠的,要想讓它去抓老鼠,就必須做向下轉(zhuǎn)型(Downcasting),也就是使用強(qiáng)制類型轉(zhuǎn)換將Animal 類型的a 引用轉(zhuǎn)換成Cat 類型的引用c(Cat c?= (Cat)a;),使用Cat 類型的c 引用調(diào)用catchMouse()方法。
通過這個(gè)案例,可以得出:只有在訪問子類型中特有數(shù)據(jù)的時(shí)候,需要先進(jìn)行向下轉(zhuǎn)型。其實(shí)向下轉(zhuǎn)型就是用在這種情形之下。那么向下轉(zhuǎn)型會(huì)存在什么風(fēng)險(xiǎn)嗎?請(qǐng)看以下代碼:

以上代碼可以編譯通過嗎?答案是可以的,為什么呢?那是因?yàn)榫幾g器只知道 a?變量是Animal?類型,Animal?類和 Cat?類之間存在繼承關(guān)系,所以可以進(jìn)行向下轉(zhuǎn)型(前面提到過, 只要兩種類型之間存在繼承關(guān)系,就可以進(jìn)行向上或向下轉(zhuǎn)型),語(yǔ)法上沒有錯(cuò)誤,所以編譯通過了。但是運(yùn)行的時(shí)候會(huì)出問題嗎,因?yàn)楫吘?a?引用指向的真實(shí)對(duì)象是一只小鳥。
來看運(yùn)行結(jié)果:

以上的異常是很常見的ClassCastException,翻譯為類型轉(zhuǎn)換異常,這種異常通常出現(xiàn)在向下轉(zhuǎn)型的操作過程當(dāng)中,當(dāng)類型不兼容的情況下進(jìn)行轉(zhuǎn)型出現(xiàn)的異常,之所以出現(xiàn)此異常是因為在程序運(yùn)行階段a 引用指向的對(duì)象是一只小鳥,然后我們要將一只小鳥轉(zhuǎn)換成一只貓,這顯然是不合理的,因?yàn)樾▲B和貓之間是沒有繼承關(guān)系的。為了避免這種異常的發(fā)生,建議在進(jìn)行向下轉(zhuǎn)型之前進(jìn)行運(yùn)行期類型判斷,這就需要我們學(xué)習(xí)一個(gè)運(yùn)算符了,它就是instanceof。
instanceof 運(yùn)算符的語(yǔ)法格式是這樣的:
(引用 instanceof 類型)
instanceof?運(yùn)算符的運(yùn)算結(jié)果是布爾類型,可能是?true,也可能是?false,假設(shè)(c?instanceof?Cat)結(jié)果是true 則表示在運(yùn)行階段c 引用指向的對(duì)象是 Cat?類型,如果結(jié)果是 false 則表示在運(yùn)行階段c 引用指向的對(duì)象不是Cat 類型。有了instanceof 運(yùn)算符,向下轉(zhuǎn)型就可以這樣寫了:

以上程序運(yùn)行之后不再發(fā)生異常,并且什么也沒有輸出,那是因?yàn)?if 語(yǔ)句的條件并沒有成立,因?yàn)樵谶\(yùn)行階段 a 引用指向的對(duì)象不是Cat 類型,所以(a instanceof Cat)是false,自然就不會(huì)進(jìn)行向下轉(zhuǎn)型了,也不會(huì)出現(xiàn) ClassCastException 異常了。在實(shí)際開發(fā)中,java 中有這樣一條默認(rèn)的規(guī)范需要大家記?。涸谶M(jìn)行任何向下轉(zhuǎn)型的操作之前,要使用instanceof 進(jìn)行判斷, 這是一個(gè)很好的編程習(xí)慣。就像下面的代碼:

運(yùn)行結(jié)果如下圖所示:

到這里大家理解什么是多態(tài)了嗎?其實(shí)多態(tài)存在的三個(gè)必要條件分別是:
繼承
方法覆蓋
父類型引用指向子類型對(duì)象
多態(tài)顯然是離不開方法覆蓋機(jī)制的,多態(tài)就是因?yàn)榫幾g階段綁定父類當(dāng)中的方法,程序運(yùn)行階段自動(dòng)調(diào)用子類對(duì)象上的方法,如果子類對(duì)象上的方法沒有進(jìn)行重寫,這個(gè)時(shí)候創(chuàng)建子類對(duì)象就沒有意義了,自然多態(tài)也就沒有意義了,只有子類將方法重寫之后調(diào)用到子類對(duì)象上的方法產(chǎn)生不同效果時(shí),多態(tài)就形成了。實(shí)際上方法覆蓋機(jī)制和多態(tài)機(jī)制是捆綁的,誰(shuí)也離不開誰(shuí),多態(tài)離不開方法覆蓋,方法覆蓋離開了多態(tài)也就沒有意義了。
接下里就來看看之前沒有解決的問題:方法覆蓋主要是說實(shí)例方法,靜態(tài)方法為什么不談方法覆蓋?

?

運(yùn)行結(jié)果如下圖所示:

我們發(fā)現(xiàn)貌似也發(fā)生了覆蓋,在程序運(yùn)行的時(shí)候確實(shí)也調(diào)用了“子類 MathSubClass”的 sum 方法,但這種“覆蓋”有意義嗎?其實(shí)上面的課程我們已經(jīng)說過了,方法覆蓋和多態(tài)機(jī)制聯(lián)合起來才有意義,我們來看看這種“覆蓋”是否能夠達(dá)到“多態(tài)”的效果,請(qǐng)看代碼:

運(yùn)行結(jié)果如下圖所示:

通過以上的代碼,我們發(fā)現(xiàn)雖然創(chuàng)建了子類型對(duì)象“new MathSubClass()”,但是程序在運(yùn)行的時(shí)候仍然調(diào)用的是Math 類當(dāng)中的sum 方法,甚至m?= null 的時(shí)候再去調(diào)用m.sum()也沒有出現(xiàn)空指針異常,這說明靜態(tài)方法的執(zhí)行壓根和對(duì)象無(wú)關(guān),既然和對(duì)象無(wú)關(guān)那就表示和多態(tài)無(wú)關(guān),既然和多態(tài)無(wú)關(guān),也就是說靜態(tài)方法的“覆蓋”是沒有意義的,所以通常我們不談靜態(tài)方法的覆蓋。
?
多態(tài)在開發(fā)中的作用
以上學(xué)習(xí)了多態(tài)的基礎(chǔ)語(yǔ)法,多態(tài)在實(shí)際開發(fā)中有什么作用呢?我們先來了解一個(gè)業(yè)務(wù)背 景:請(qǐng)?jiān)O(shè)計(jì)一個(gè)系統(tǒng),描述主人喂養(yǎng)寵物的場(chǎng)景,首先在這個(gè)場(chǎng)景當(dāng)中應(yīng)該有“寵物對(duì)象”, 寵物對(duì)象應(yīng)該有一個(gè)吃的行為,另外還需要一個(gè)“主人對(duì)象”,主人對(duì)象應(yīng)該有一個(gè)喂的行為, 請(qǐng)看代碼:


運(yùn)行結(jié)果如下圖所示:

以上程序編譯和運(yùn)行都很正常,輸出結(jié)果也是對(duì)的,那么存在什么問題嗎?假設(shè)后期用戶提出了新的需求,軟件可能面臨著功能擴(kuò)展,這個(gè)擴(kuò)展會(huì)很方便嗎?假設(shè)現(xiàn)在主人家里又來了一個(gè)寵物貓,那該怎么辦呢?請(qǐng)看代碼:
在以上代碼的基礎(chǔ)之上,新增了一個(gè)Cat 類,來表示寵物貓,這個(gè)對(duì)于程序來說是可以接受的:

另外,除了增加一個(gè)Cat 類之外,我們還需要“修改”Master 主人類的源代碼,這件事兒是我們程序員無(wú)法容忍的,因?yàn)樾薷闹皩懞玫脑创a就面臨著重新編譯、重新全方位的測(cè)試,?這是一個(gè)巨大的工作,維護(hù)成本很高,也很麻煩:


?運(yùn)行結(jié)果如下圖所示:

在軟件開發(fā)過程中,有這樣的一個(gè)開發(fā)原則:開閉原則。開閉原則(OCP)是面向?qū)ο笤O(shè) 計(jì)中“可復(fù)用設(shè)計(jì)”的基石,是面向?qū)ο笤O(shè)計(jì)中最重要的原則之一,其它很多的設(shè)計(jì)原則都是 實(shí)現(xiàn)開閉原則的一種手段。
1988??年,勃蘭特·梅耶(Bertrand Meyer)在他的著作《面向?qū)ο筌浖?gòu)造(Object Oriented Software?Construction)》中提出了開閉原則,它的原文是這樣: “Software?entities?should?be?open?for?extension,but?closed?for?modification”。翻譯過來就是:“軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉”。這句話說得略微有點(diǎn)專業(yè),我們把它講得更通俗一點(diǎn), 也就是:軟件系統(tǒng)中包含的各種組件,例如模塊(Modules)、類(Classes)以及功能(Functions) 等等,應(yīng)該在不修改現(xiàn)有代碼的基礎(chǔ)上,引入新功能。
開閉原則中“開”,是指對(duì)于組件功能 的擴(kuò)展是開放的,是允許對(duì)其進(jìn)行功能擴(kuò)展的;開閉原則中“閉”,是指對(duì)于原有代碼的修改 是封閉的,即修改原有的代碼對(duì)外部的使用是透明的。
以上程序在擴(kuò)展的過程當(dāng)中就違背了 OCP 原則,因?yàn)樵跀U(kuò)展的過程當(dāng)中修改了已經(jīng)寫好的Master 類,怎樣可以解決這個(gè)問題呢?多態(tài)可以解決,請(qǐng)看代碼:





運(yùn)行結(jié)果如下圖所示:

在以上程序中,Master 類中的方法feed(Pet pet)的參數(shù)類型定義為更加抽象的Pet 類型,而不是具體Dog 寵物,或者 Cat 寵物,顯然 Master 類和具體的Dog、Cat 類解耦合了,依賴性弱了,這就是我們通常所說的面向抽象編程,盡量不要面向具體編程,面向抽象編程會(huì)讓你的代碼耦合度降低,擴(kuò)展能力增強(qiáng),從而符合 OCP 的開發(fā)原則。
假如說這會(huì)再來一個(gè)新的寵物豬呢,我們只需要這樣做,新增加一個(gè)“寵物豬類”,然后寵物豬類 Pig?繼承寵物類 Pet,并重寫 eat()方法,然后修改一下測(cè)試類就行了,整個(gè)過程我們是不需要修改 Master 類的,只是額外增加了一個(gè)新的類:

運(yùn)行結(jié)果如下圖所示:

以上程序中到底哪里使用了多態(tài)機(jī)制呢?請(qǐng)看下圖:

通過以上內(nèi)容的學(xué)習(xí),我們可以看到多態(tài)在開發(fā)中聯(lián)合方法覆蓋一起使用,可以降低程序的耦合度,提高程序的擴(kuò)展力。在開發(fā)中盡可能面向抽象編程,不要面向具體編程,好比電腦主板和內(nèi)存條的關(guān)系一樣,主板和內(nèi)存條件之間有一個(gè)抽象的符合某個(gè)規(guī)范的插槽,不同品牌的內(nèi)存條都可以插到主板上使用,2 個(gè)G 的內(nèi)存條和 4 個(gè)G 的內(nèi)存條都可以插上,但最終的表現(xiàn)結(jié)果是不同的,2 個(gè)G 的內(nèi)存條處理速度慢一些,4 個(gè)G 的快一些,這就是多態(tài),所謂多態(tài)就是同一個(gè)行為作用到不同的對(duì)象上,最終的表現(xiàn)結(jié)果是不同的,主要的要求就是對(duì)象是可以進(jìn)行靈活切換的,靈活切換的前提就是解耦合,解耦合依賴多態(tài)機(jī)制。
難點(diǎn)解惑
本章節(jié)的難點(diǎn)就是對(duì)多態(tài)機(jī)制的理解,多態(tài)的代碼表現(xiàn)是父類型引用指向子類型對(duì)象,對(duì) 于多態(tài)的理解一定要分為編譯階段和運(yùn)行階段來進(jìn)行分析,編譯階段只是看父類型中是否存在 要調(diào)用的方法,如果父類中不存在,則編譯器會(huì)報(bào)錯(cuò),編譯階段和具體 new 的對(duì)象無(wú)關(guān)。但是在運(yùn)行階段就要看底層具體new?的是哪個(gè)類型的子對(duì)象了,new?的這個(gè)子類型對(duì)象可以看做 “真實(shí)對(duì)象”,自然在運(yùn)行階段就會(huì)調(diào)用真實(shí)對(duì)象的相關(guān)方法。例如代碼:Animal a?= new Cat(); a.move();,在編譯階段編譯器只能檢測(cè)到a的類型是Animal,所以一定會(huì)去Animal?類中找move()方法,如果Animal?中沒有move()方法,則編譯器會(huì)報(bào)錯(cuò),即使 Cat 中有move()方法,也會(huì)報(bào)錯(cuò),因?yàn)榫幾g器只知道 a 的類型是Animal 類,只有在運(yùn)行的時(shí)候,實(shí)際創(chuàng)建的真實(shí)對(duì)象是 Cat, 那么在運(yùn)行的時(shí)候就會(huì)自動(dòng)調(diào)用Cat??對(duì)象的move()方法。這樣就可以達(dá)到多種形態(tài),也就是說編譯階段一種形態(tài),運(yùn)行階段的時(shí)候是另一種形態(tài)。這也就是多態(tài)的含義。
?
小結(jié)
通過本章節(jié)內(nèi)容的學(xué)習(xí),大家需要理解什么時(shí)候考慮使用方法覆蓋,理解滿足什么條件時(shí)能夠構(gòu)成方法覆蓋,以及掌握怎么進(jìn)行方法覆蓋。另外還需要掌握多態(tài)相關(guān)的基礎(chǔ)語(yǔ)法,理解多態(tài)在開發(fā)中起到了解耦合的作用,提倡面向抽象編程,不要面向具體編程。理解?OCP?開發(fā)原則等。
最后附Java零基礎(chǔ)視頻教程給大家,配合學(xué)習(xí)效果更佳!!

