先說一下多態(tài)這個(gè)名稱,有些文章里把重載(overloading)稱為編譯時(shí)多態(tài),把覆蓋(overriding)稱為運(yùn)行時(shí)多態(tài)。我這里的多態(tài)只涉及overriding。
重載Overloading是在一個(gè)類中定義了一個(gè)以上具有相同名稱,但是型構(gòu)不同的方法。
如:
public int? add(int i,int n);
public float add(float i ,float n);
覆蓋Overriding簡單指子類中定義了和超類中具有相同型構(gòu)的方法。
如:超類Person中有個(gè)public void showmyface(String input);
子類BirdPerson中又定義了一個(gè)public void showmyface(String input);
正因?yàn)橛辛烁采w才有了架構(gòu)上的靈活性。
通過一個(gè)項(xiàng)目例子需求出發(fā),遇到困難,引出多態(tài)的意義所在。
一個(gè)鳥類展示項(xiàng)目,用戶可以點(diǎn)擊,查詢各種鳥類,軟件展示用戶所要看的鳥類信息。
按照面向?qū)ο笤O(shè)計(jì)原則,我們可以按照生物學(xué)上的類體系結(jié)構(gòu),設(shè)計(jì)超類、子類。其中courtship()方法是鳥類求偶方式介紹。因?yàn)轼B類都有courtship();所以超類里也放了courtship()。類層次越往上層,courtship()方法介紹的越是泛泛。到了最底層類的courtship()方法,則最具體描述細(xì)節(jié)。
鳥類體系建好后,還有一個(gè)就是展示類了。
public Class DisplayBird{
...//這里給用戶展現(xiàn)鳥類目錄,讓用戶瀏覽選擇鳥類,或搜索。選擇后通過名字到工廠創(chuàng)建具體對(duì)象
Public void compareCourtship(...);//分別展示所選的2種鳥類的courtship(),便于對(duì)比學(xué)習(xí);
...
}
compareCourtship(...)方法如果不用多態(tài),而用重載,那就得這么做
...
Public void compareCourtship(Parrot parrot,Cock cock){...};
Public void compareCourtship(Parrot parrot,CrazyParrot crazyParrot){...};
...
以后每次增加一種鳥類,就要增加一批重載方法。
有了多態(tài)就簡單了。
Public void compareCourtship(Bird bird1,Bird bird2){...};
具體使用時(shí):
compareCourtship(new Cock(),new Parrot());
在compareCourtship里調(diào)用bird1.courtship(),就是展現(xiàn)Cock的courtship().bird2.courtship(),就是展現(xiàn)Parrot的courtship();
有了多態(tài)后結(jié)構(gòu)簡單,增加新的鳥類,也無需改動(dòng)先前的代碼。
當(dāng)然,我們也可以把Bird里的courtship();設(shè)為抽象方法。或者抽取出來作為接口。這個(gè)接口也可以放到哺乳動(dòng)物。
所以,多態(tài),首先是類族體系里某種行為(方法)的算法多樣性。這種方法不是某個(gè)特殊子類的特別行為,而是一些類共有的方法。所以把這種方法放到超類或抽象獨(dú)立設(shè)計(jì)為接口。在不同子類中實(shí)現(xiàn)不同的算法。
從這個(gè)例子可以看出,多態(tài)在設(shè)計(jì)程序時(shí),如果軟件架構(gòu)設(shè)計(jì)中需要依賴某個(gè)對(duì)象及其方法時(shí),要考慮這個(gè)對(duì)象方法是否可以多態(tài)。這就是設(shè)計(jì)原則里-依賴倒置原則-意義所在。要依賴超類(抽象類、接口)不要依賴具體類,基于接口的開發(fā),而不是基于實(shí)現(xiàn)的開發(fā)。
多態(tài)的實(shí)現(xiàn)原理
Java 里對(duì)象方法的調(diào)用是依靠類信息里的方法表實(shí)現(xiàn)的,雖然對(duì)象方法引用調(diào)用和接口方法引用調(diào)用的實(shí)現(xiàn)有所不同但大致思想是一樣的。總體而言,當(dāng)調(diào)用對(duì)象某個(gè)方法時(shí),JVM查找該對(duì)象類的方法表以確定該方法的直接引用地址,有了地址后才真正調(diào)用該方法。
java程序運(yùn)行時(shí)虛擬機(jī)、類、對(duì)象的內(nèi)存結(jié)構(gòu)細(xì)節(jié)看這里》》
我們知道java程序運(yùn)行時(shí),類的相關(guān)信息放在方法區(qū),在這些信息中有個(gè)叫方法表的區(qū)域,該表包含有該類型所定義的所有方法的信息和指向這些方法實(shí)際代碼的指針。
當(dāng)Bird、Cock、Parrot和CrazyParrot這四個(gè)類被加載到 Java 虛擬機(jī)之方法區(qū)后,方法區(qū)中就包含了這四個(gè)類的信息,下圖示例了各個(gè)類的方法表。
從圖我們可以看到Cock、Parrot和CrazyParrot的類信息方法表包含了繼承自Bird的方法。CrazyParrot的方法表包含了繼承自Parrot的方法。此外各個(gè)類也有自己的方法。
注意看,方法表?xiàng)l目指向的具體方法代碼區(qū)。對(duì)于多態(tài)Overriding的方法courtship(),雖然Cock、Parrot和CrazyParrot的方法表里的courtship()條目所在位置是屬于繼承自Bird方法表的部分,但指向不同的方法代碼區(qū)了。
所以:
public void compareCourtship(Bird bird1,Bird bird2);
在compareCourtship(new Cock(),new Parrot());調(diào)用時(shí),bird1.courtship();和bird2.courtship();會(huì)有多態(tài)效果。
對(duì)于接口方式的多態(tài),方法調(diào)用所涉及方法表搜索算法會(huì)有所區(qū)別,但大致的原理是類似的。