站長(zhǎng)資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

相關(guān)學(xué)習(xí)推薦:java基礎(chǔ)教程

今天,跟往常一樣踩點(diǎn)來(lái)到了公司。坐到自己的工位上打開電腦,"又是搬磚的一天"。想歸想,還是"熟練"的打開了 Idea,看了下今天的需求,便敲起了代碼。咦,這些代碼是誰(shuí)寫的,怎么出現(xiàn)在我的代碼里面,而且還是待提交狀態(tài),我記得我沒(méi)寫過(guò)呀,饒有興趣的看了看:

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

這不是多態(tài)嗎,誰(shuí)在我電腦寫的測(cè)試,不禁一陣奇怪。

"你看看這會(huì)輸出什么結(jié)果?"

一陣聲音從身后傳來(lái),因?yàn)樵谒伎驾敵鼋Y(jié)果,也沒(méi)在意聲音的來(lái)源,繼續(xù)看了看代碼,便得出結(jié)論:

    polygon() before cal()     square.cal(), border = 2     polygon() after cal()     square.square(), border = 4復(fù)制代碼

心里想:就這?起碼也是名 Java 開發(fā)工程師好嗎,雖然平時(shí)搬搬磚,一些基本功還是有的。不禁有點(diǎn)得意了~

"這就是你的答案嗎?看來(lái)你也不咋的"

聲音又突然響起,這次我不淡定了,尼瑪!這答案我也是在心里想的好嗎,誰(shuí)能看得到啊,而且說(shuō)得話讓人那么想施展一套阿威十八式。"你是誰(shuí)啊?"帶著絲微疑惑和憤怒轉(zhuǎn)過(guò)了頭。怎么沒(méi)人?容不得我疑惑半分,"小菜,醒醒,你怎么上班時(shí)間就睡著了"

上班時(shí)間,睡著了?我睜開了眼,看了下周圍環(huán)境,原來(lái)是夢(mèng)啊,舒了一口氣。望眼就看到部門主管站在我面前,上班時(shí)間睡覺(jué),你是身體不舒服還是咋樣?昨天寫了一堆 bug 沒(méi)改,今天又提交什么亂七八糟的東西上去,我看你這個(gè)月的績(jī)效是不想要的,而且基于你的表現(xiàn),我也要開始為部門考慮考慮了。

"我不是,我沒(méi)有,我也不知道怎么就睡著了,你聽我解釋啊!" 這句話還沒(méi)來(lái)得及說(shuō)出口,心里的花我要帶你回家,在那深夜酒吧哪管它是真是假,請(qǐng)你盡情搖擺忘記鐘意的他,你是最迷人噶,你知道嗎,鬧鈴響了起來(lái),我一下子立起身子,后背微濕,額頂微汗,看了下手機(jī),周六,8點(diǎn)30分,原來(lái)那是夢(mèng)啊!

奇怪,怎么會(huì)做那么奇怪的夢(mèng),也太嚇人了。然后就想到了夢(mèng)中的那部分代碼,難道我的結(jié)果是錯(cuò)的嗎?憑著記憶,在電腦上重新敲了出來(lái),運(yùn)行結(jié)果如下:

/*     polygon() before cal()     square.cal(), border = 0     polygon() after cal()     square.square(), border = 4 */復(fù)制代碼

square.cal(), border的結(jié)果居然是 0,而不是2。難道我現(xiàn)在連多態(tài)都不會(huì)了嗎?電腦手機(jī)前的你,不知道是否得出了正確答案了呢!不管有沒(méi)有,接下來(lái)就跟小菜一起來(lái)復(fù)習(xí)一下多態(tài)吧!

有些小伙伴疑惑的點(diǎn)可能不止square.cal(), border的結(jié)果是 0,也有為什么不是 square.square(), border = 4 先輸出的疑惑。那么我們就帶著疑惑,整起!

多態(tài)

在面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言中,多態(tài)是繼數(shù)據(jù)抽象和繼承之后的第三種基本特征。

多態(tài)不但能夠改善代碼的組織結(jié)構(gòu)和可讀性,還能夠創(chuàng)建可擴(kuò)展的程序。多態(tài)的作用就是消除類型之間的耦合關(guān)系

1. 向上轉(zhuǎn)型

根據(jù)里氏代換原則:任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。

對(duì)象既可以作為它自己本身的類型使用,也可以作為它的基類型使用。而這種吧對(duì)某個(gè)對(duì)象的引用視為對(duì)其基類型的引用的做法被稱作為 – 向上轉(zhuǎn)型。因?yàn)楦割愒谧宇惖纳戏剑宇愐酶割悾虼朔Q為 向上轉(zhuǎn)型

public class Animal {    void eat() {         System.out.println("Animal eat()");     } }class Monkey extends Animal {    void eat() {         System.out.println(" Monkey eat()");     } }class test {    public static void start(Animal animal) {         animal.eat();     }    public static void main(String[] args) {         Monkey monkey = new Monkey();         start(monkey);     } }/* OUTPUT: Monkey eat() */復(fù)制代碼

上述 test 類中的 start() 方法接收一個(gè) Animal 的引用,自然也可以接收從 Animal 的導(dǎo)出類。調(diào)用eat() 方法的時(shí)候,自然而然的使用到 Monkey 中定義的eat()方法,而不需要做任何的類型轉(zhuǎn)換。因?yàn)閺?Monkey 向上轉(zhuǎn)型到 Animal 只能減少接口,而不會(huì)比Animal 的接口更少。

打個(gè)不是特別恰當(dāng)?shù)谋确剑?em>你父親的財(cái)產(chǎn)會(huì)繼承給你,而你的財(cái)產(chǎn)還是你的,總的來(lái)說(shuō),你的財(cái)產(chǎn)不會(huì)比你父親的少。

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

忘記對(duì)象類型

test.start()方法中,定義傳入的是 Animal 的引用,但是卻傳入Monkey,這看起來(lái)似乎忘記了Monkey 的對(duì)象類型,那么為什么不直接把test類中的方法定義為void start(Monkey monkey),這樣看上去難道不會(huì)更直觀嗎。

直觀也許是它的優(yōu)點(diǎn),但是就會(huì)帶來(lái)其他問(wèn)題:Animal不止只有一個(gè)Monkey的導(dǎo)出類,這個(gè)時(shí)候來(lái)了個(gè)pig ,那么是不是就要再定義個(gè)方法為void start(Monkey monkey),重載用得挺溜嘛小伙子,但是未免太麻煩了。懶惰才是開發(fā)人員的天性。

因此這樣就有了多態(tài)的產(chǎn)生

2.顯露優(yōu)勢(shì)

方法調(diào)用中分為 靜態(tài)綁定動(dòng)態(tài)綁定。何為綁定:將一個(gè)方法調(diào)用同一個(gè)方法主體關(guān)聯(lián)起來(lái)被稱作綁定。

  • 靜態(tài)綁定:又稱為前期綁定。是在程序執(zhí)行前進(jìn)行把綁定。我們平時(shí)聽到"靜態(tài)"的時(shí)候,不難免想到static關(guān)鍵字,被static關(guān)鍵字修飾后的變量成為靜態(tài)變量,這種變量就是在程序執(zhí)行前初始化的。前期綁定是面向過(guò)程語(yǔ)言中默認(rèn)的綁定方式,例如 C 語(yǔ)言只有一種方法調(diào)用,那就是前期綁定。

引出思考:

public static void start(Animal animal) {     animal.eat(); }復(fù)制代碼

start()方法中傳入的是Animal 的對(duì)象引用,如果有多個(gè)Animal的導(dǎo)出類,那么執(zhí)行eat()方法的時(shí)候如何知道調(diào)用哪個(gè)方法。如果通過(guò)前期綁定那么是無(wú)法實(shí)現(xiàn)的。因此就有了后期綁定

  • 動(dòng)態(tài)綁定:又稱為后期綁定。是在程序運(yùn)行時(shí)根據(jù)對(duì)象類型進(jìn)行綁定的,因此又可以稱為運(yùn)行時(shí)綁定。而 Java 就是根據(jù)它自己的后期綁定機(jī)制,以便在運(yùn)行時(shí)能夠判斷對(duì)象的類型,從而調(diào)用正確的方法。

小結(jié):

Java 中除了 staticfinal 修飾的方法之外,都是屬于后期綁定

合理即正確

顯然通過(guò)動(dòng)態(tài)綁定來(lái)實(shí)現(xiàn)多態(tài)是合理的。這樣子我們?cè)陂_發(fā)接口的時(shí)候只需要傳入 基類 的引用,從而這些代碼對(duì)所有 基類 的 導(dǎo)出類 都可以正確的運(yùn)行。

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

其中MonkeyPigDog皆是Animal的導(dǎo)出類

Animal animal = new Monkey() 看上去不正確的賦值,但是上通過(guò)繼承,Monkey就是一種Animal,如果我們調(diào)用animal.eat()方法,不了解多態(tài)的小伙伴常常會(huì)誤以為調(diào)用的是Animaleat()方法,但是最終卻是調(diào)用了Monkey自己的eat()方法。

Animal作為基類,它的作用就是為導(dǎo)出類建立公用接口。所有從Animal繼承出去的導(dǎo)出類都可以有自己獨(dú)特的實(shí)現(xiàn)行為。

可擴(kuò)展性

有了多態(tài)機(jī)制,我們可以根據(jù)自己的需求對(duì)系統(tǒng)添加任意多的新類型,而不需要重載void start(Animal animal)方法。

在一個(gè)設(shè)計(jì)良好的OOP程序中,大多數(shù)或者所有方法都會(huì)遵循start()方法的模型,只與基類接口同行,這樣的程序就是具有可擴(kuò)展性的,我們可以通過(guò)從通用的基類繼承出新的數(shù)據(jù)類型,從而添加一些功能,那些操縱基類接口的方法就不需要任何改動(dòng)就可以應(yīng)用于新類。

失靈了?

我們先來(lái)復(fù)習(xí)一下權(quán)限修飾符:

作用域 當(dāng)前類 用一個(gè)package 子孫類 其他package
public
protected ×
default × ×
private × × ×
  • public:所有類可見
  • protected:本類、本包和子類都可見
  • default:本類和本包可見
  • private:本類可見

私有方法帶來(lái)的失靈

復(fù)習(xí)完我們?cè)賮?lái)看一組代碼:

public class PrivateScope {    private void f() {         System.out.println("PrivateScope f()");     }    public static void main(String[] args) {         PrivateScope p = new PrivateOverride();         p.f();     } }class PrivateOverride extends PrivateScope {    private void f() {         System.out.println("PrivateOverride f()");     } }/* OUTPUT  PrivateScope f() */復(fù)制代碼

是否感到有點(diǎn)奇怪,為什么這個(gè)時(shí)候調(diào)用的f()是基類中定義的,而不像上面所述的那樣,通過(guò)動(dòng)態(tài)綁定,從而調(diào)用導(dǎo)出類PrivateOverride中定義的f()。不知道心細(xì)的你是否發(fā)現(xiàn),基類中f()方法的修飾是private。沒(méi)錯(cuò),這就是問(wèn)題所在,PrivateOverride中定義的f()方法是一個(gè)全新的方法,因?yàn)?code>private的緣故,對(duì)子類不可見,自然也不能被重載。

結(jié)論

只有非 private 修飾的方法才可以被覆蓋

我們通過(guò) Idea 寫代碼的時(shí)候,重寫的方法頭上可以標(biāo)注@Override注解,如果不是重寫的方法,標(biāo)注@Override注解就會(huì)報(bào)錯(cuò):

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

這樣也可以很好的提示我們非重寫方法,而是全新的方法。

域帶來(lái)的失靈

當(dāng)小伙伴看到這里,就會(huì)開始認(rèn)為所有事物(除private修飾)都可以多態(tài)地發(fā)生。然而現(xiàn)實(shí)卻不是這樣子的,只有普通的方法調(diào)用才可以是多態(tài)的。這邊是多態(tài)的誤區(qū)所在。

讓我們?cè)倏纯聪旅孢@組代碼:

class Super {    public int field = 0;    public int getField() {        return field;     } }class Son extends Super {    public int field = 1;    public int getField() {        return field;     }    public int getSuperField() {        return super.field;     } }class FieldTest {    public static void main(String[] args) {         Super sup = new Son();         System.out.println("sup.field:" + sup.field + " sup.getField():" + sup.getField());          Son son = new Son();         System.out.println("son.field:" + son.field + " son.getField:" + son.getField() + " son.getSupField:" + son.getSuperField());     } }/* OUTPUT sup.field:0 sup.getField():1 son.field:1 son.getField:1 son.getSupField:0 */復(fù)制代碼

從上面代碼中我們看到sup.field輸出的值不是 Son 對(duì)象中所定義的,而是Super本身定義的。這與我們認(rèn)識(shí)的多態(tài)有點(diǎn)沖突。

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

其實(shí)不然,當(dāng)Super對(duì)象轉(zhuǎn)型為Son引用時(shí),任何域訪問(wèn)操作都將由編譯器解析,因此不是多態(tài)的。在本例中,為Super.fieldSon.field分配了不同的存儲(chǔ)空間,而Son類是從Super類導(dǎo)出的,因此,Son實(shí)際上是包含兩個(gè)稱為field的域:它自己的+Super

雖然這種問(wèn)題看上去很令人頭痛,但是我們開發(fā)規(guī)范中,通常會(huì)將所有的域都設(shè)置為 private,這樣就不能直接訪問(wèn)它們,只能通過(guò)調(diào)用方法來(lái)訪問(wèn)。

static 帶來(lái)的失靈

看到這里,小伙伴們應(yīng)該對(duì)多態(tài)有個(gè)大致的了解,但是不要掉以輕心哦,還有一種情況也是會(huì)出現(xiàn)失靈的,那就是如果某個(gè)方法是靜態(tài)的,那么它的行為就不具有多態(tài)性。

老規(guī)矩,我們看下這組代碼:

class StaticSuper {    public static void staticTest() {         System.out.println("StaticSuper staticTest()");     }  }class StaticSon extends StaticSuper{    public static void staticTest() {         System.out.println("StaticSon staticTest()");     }  }class StaticTest {    public static void main(String[] args) {         StaticSuper sup = new StaticSon();         sup.staticTest();     } }/* OUTPUT StaticSuper staticTest() */復(fù)制代碼

靜態(tài)方法是與類相關(guān)聯(lián),而非與對(duì)象相關(guān)聯(lián)

3.構(gòu)造器與多態(tài)

首先我們需要明白的是構(gòu)造器不具有多態(tài)性,因?yàn)闃?gòu)造器實(shí)際上是static方法,只不過(guò)該static的聲明是隱式的。

我們先回到開頭的那段神秘代碼:

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

其中輸出結(jié)果是:

/*     polygon() before cal()     square.cal(), border = 0     polygon() after cal()     square.square(), border = 4 */復(fù)制代碼

我們可以看到先輸出的是基類polygon中構(gòu)造器的方法。

這是因?yàn)榛惖臉?gòu)造器總是在導(dǎo)出類的構(gòu)造過(guò)程中被調(diào)用,而且是按照繼承層次逐漸向上鏈接,以使每個(gè)基類的構(gòu)造器都能得到調(diào)用。

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

因?yàn)闃?gòu)造器有一項(xiàng)特殊的任務(wù):檢查對(duì)象是否能正確的被構(gòu)造。導(dǎo)出類只能訪問(wèn)它自己的成員,不能訪問(wèn)基類的成員(基類成員通常是private類型)。只有基類的構(gòu)造器才具有權(quán)限來(lái)對(duì)自己的元素進(jìn)行初始化。因此,必須令所有構(gòu)造器都得到調(diào)用,否則就不可能正確構(gòu)造完整對(duì)象。

步驟如下:

  • 調(diào)用基類構(gòu)造器,這個(gè)步驟會(huì)不斷的遞歸下去,首先是構(gòu)造這種層次結(jié)構(gòu)的根,然后是下一層導(dǎo)出類,…,直到最底層的導(dǎo)出類
  • 按聲明順序調(diào)用成員的初始化方法
  • 調(diào)用導(dǎo)出類構(gòu)造其的主體

打個(gè)不是特別恰當(dāng)?shù)谋确剑?em>你的出現(xiàn)是否先要有你父親,你父親的出現(xiàn)是否先要有你的爺爺,這就是逐漸向上鏈接的方式

構(gòu)造器內(nèi)部的多態(tài)行為

有沒(méi)有想過(guò)如果在一個(gè)構(gòu)造器的內(nèi)調(diào)用正在構(gòu)造的對(duì)象的某個(gè)動(dòng)態(tài)綁定方法,那么會(huì)發(fā)生什么情況呢? 動(dòng)態(tài)綁定的調(diào)用是在運(yùn)行時(shí)才決定的,因?yàn)閷?duì)象無(wú)法知道它是屬于方法所在的那個(gè)類還是那個(gè)類的導(dǎo)出類。如果要調(diào)用構(gòu)造器內(nèi)部的一個(gè)動(dòng)態(tài)綁定方法,就要用到那個(gè)方法的被覆蓋后的定義。然而因?yàn)楸桓采w的方法在對(duì)象被完全構(gòu)造之前就會(huì)被調(diào)用,這可能就會(huì)導(dǎo)致一些難于發(fā)現(xiàn)的隱藏錯(cuò)誤。

問(wèn)題引索

一個(gè)動(dòng)態(tài)綁定的方法調(diào)用會(huì)向外深入到繼承層次結(jié)構(gòu)內(nèi)部,它可以調(diào)動(dòng)導(dǎo)出類里的方法,如果我們是在構(gòu)造器內(nèi)部這樣做,那么就可能會(huì)調(diào)用某個(gè)方法,而這個(gè)方法做操縱的成員可能還未進(jìn)行初始化,這肯定就會(huì)招致災(zāi)難的。

敏感的小伙伴是不是想到了開頭的那段代碼:

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

輸出結(jié)果是:

/*     polygon() before cal()     square.cal(), border = 0     polygon() after cal()     square.square(), border = 4 */復(fù)制代碼

我們?cè)谶M(jìn)行square對(duì)象初始化的時(shí)候,會(huì)先進(jìn)行polygon對(duì)象的初始化,在polygon構(gòu)造器中有個(gè)cal()方法,這個(gè)時(shí)候就采用了動(dòng)態(tài)綁定機(jī)制,調(diào)用了squarecal(),但這個(gè)時(shí)候border這個(gè)變量尚未進(jìn)行初始化,int 類型的默認(rèn)值為 0,因此就有了square.cal(), border = 0的輸出。看到這里,小伙伴們是不是有種撥開云霧見青天的感覺(jué)!

這組代碼初始化的實(shí)際過(guò)程為:

  • 在其他任何事物發(fā)生之前,將分配給對(duì)象的存儲(chǔ)空間初始化成二進(jìn)制的零
  • 調(diào)用基類構(gòu)造器時(shí),會(huì)調(diào)用被覆蓋后的cal()方法,由于步驟1的緣故,因此 border 的值為 0
  • 按照聲明的順序調(diào)用成員的初始化方法
  • 調(diào)用導(dǎo)出類的構(gòu)造器主體

不知道下次又會(huì)做什么樣的夢(mèng)~

這年頭,說(shuō)自己會(huì)Java就得會(huì)多態(tài)

想了解

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
精品人妻va出轨中文字幕| 国内精品51视频在线观看| 在线观看亚洲精品专区| 69久久夜色精品国产69| 久久精品国产亚洲AV大全| 国产精品女同一区二区| 在线中文字幕精品第5页| 国产2021久久精品| 青娱乐国产精品视频| 日韩有码在线视频| 亚洲一区日韩高清中文字幕亚洲| 国产精品无码久久四虎| 8AV国产精品爽爽ⅴa在线观看| 亚洲精品国产情侣av在线| 亚洲人成亚洲精品| 麻豆精品久久精品色综合| 亚洲国产一成人久久精品| 国产午夜亚洲精品国产成人小说| 国产成人综合久久精品尤物| 精品乱码一区内射人妻无码| 日韩欧美一区二区三区免费观看| 国产精品国产免费无码专区不卡 | 日韩精品亚洲人成在线观看| 国产精品区一区二区三在线播放| 国语自产偷拍精品视频偷拍| 在线精品亚洲一区二区三区| 99久久这里只有精品| 国产女人水真多18毛片18精品| 在线观看亚洲精品福利片 | AAA级久久久精品无码片| 67194老司机精品午夜| 亚洲国产精品白丝在线观看 | 精品乱码一区二区三区在线| 精品在线一区二区三区| 狠狠热精品免费观看| www亚洲精品少妇裸乳一区二区| 精品一区二区三区电影| 亚洲国产精品成人| 久久精品国产亚洲Aⅴ香蕉| 少妇伦子伦精品无吗| 精品视频一区二区三区在线观看|