1.理解java的三大特性之多態(tài)
面向?qū)ο缶幊逃腥筇匦裕悍庋b、繼承环凿、多態(tài)梧兼。
封裝隱藏了類(lèi)的內(nèi)部實(shí)現(xiàn)機(jī)制,可以在不影響使用的情況下改變類(lèi)的內(nèi)部結(jié)構(gòu)智听,同時(shí)也保護(hù)了數(shù)據(jù)羽杰。對(duì)外界而已它的內(nèi)部細(xì)節(jié)是隱藏的,暴露給外界的只是它的訪(fǎng)問(wèn)方法瞭稼。
繼承是為了重用父類(lèi)代碼忽洛。兩個(gè)類(lèi)若存在IS-A的關(guān)系就可以使用繼承。环肘,同時(shí)繼承也為實(shí)現(xiàn)多態(tài)做了鋪墊欲虚。那么什么是多態(tài)呢?多態(tài)的實(shí)現(xiàn)機(jī)制又是什么悔雹?請(qǐng)看我一一為你揭開(kāi):
所謂多態(tài)就是指程序中定義的引用變量所指向的具體類(lèi)型和通過(guò)該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定复哆,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會(huì)指向哪個(gè)類(lèi)的實(shí)例對(duì)象腌零,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類(lèi)中實(shí)現(xiàn)的方法梯找,必須在由程序運(yùn)行期間才能決定。因?yàn)樵诔绦蜻\(yùn)行時(shí)才確定具體的類(lèi)益涧,這樣锈锤,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類(lèi)實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變久免,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼浅辙,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性阎姥。
比如你是一個(gè)酒神记舆,對(duì)酒情有獨(dú)鐘。某日回家發(fā)現(xiàn)桌上有幾個(gè)杯子里面都裝了白酒呼巴,從外面看我們是不可能知道這是些什么酒泽腮,只有喝了之后才能夠猜出來(lái)是何種酒。你一喝衣赶,這是劍南春诊赊、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:
酒 a = 劍南春
酒 b = 五糧液
酒 c = 酒鬼酒
…
這里所表現(xiàn)的的就是多態(tài)屑埋。劍南春豪筝、五糧液、酒鬼酒都是酒的子類(lèi)摘能,我們只是通過(guò)酒這一個(gè)父類(lèi)就能夠引用不同的子類(lèi),這就是多態(tài)——我們只有在運(yùn)行的時(shí)候才會(huì)知道引用變量所指向的具體實(shí)例對(duì)象敲街。
誠(chéng)然团搞,要理解多態(tài)我們就必須要明白什么是“向上轉(zhuǎn)型”。在繼承中我們簡(jiǎn)單介紹了向上轉(zhuǎn)型多艇,這里就在啰嗦下:在上面的喝酒例子中逻恐,酒(Win)是父類(lèi),劍南春(JNC)峻黍、五糧液(WLY)复隆、酒鬼酒(JGJ)是子類(lèi)。我們定義如下代碼:
JNC a = new? JNC();
對(duì)于這個(gè)代碼我們非常容易理解無(wú)非就是實(shí)例化了一個(gè)劍南春的對(duì)象嘛姆涩!但是這樣呢挽拂?
Wine a = new JNC();
在這里我們這樣理解,這里定義了一個(gè)Wine 類(lèi)型的a骨饿,它指向JNC對(duì)象實(shí)例亏栈。由于JNC是繼承與Wine,所以JNC可以自動(dòng)向上轉(zhuǎn)型為Wine宏赘,所以a是可以指向JNC實(shí)例對(duì)象的绒北。這樣做存在一個(gè)非常大的好處,在繼承中我們知道子類(lèi)是父類(lèi)的擴(kuò)展察署,它可以提供比父類(lèi)更加強(qiáng)大的功能闷游,如果我們定義了一個(gè)指向子類(lèi)的父類(lèi)引用類(lèi)型,那么它除了能夠引用父類(lèi)的共性外,還可以使用子類(lèi)強(qiáng)大的功能脐往。
但是向上轉(zhuǎn)型存在一些缺憾休吠,那就是它必定會(huì)導(dǎo)致一些方法和屬性的丟失,而導(dǎo)致我們不能夠獲取它們钙勃。
具體代碼:
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}public void fun2(){
System.out.println("Wine 的Fun2...");
}
}public class JNC extends Wine{/*** @desc 子類(lèi)重載父類(lèi)方法
*? ? ? ? 父類(lèi)中不存在該方法蛛碌,向上轉(zhuǎn)型后,父類(lèi)是不能引用該方法的
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}/*** 子類(lèi)重寫(xiě)父類(lèi)方法
* 指向子類(lèi)的父類(lèi)引用調(diào)用fun2時(shí)辖源,必定是調(diào)用該方法*/publicvoidfun2(){
System.out.println("JNC 的Fun2...");
}
}public class? Test {
public static void main(String[] args) {
Wine a=newJNC();
a.fun1();
}
}
Output:
Wine 的Fun.....
JNC 的Fun2...
從程序的運(yùn)行結(jié)果中我們發(fā)現(xiàn)蔚携,a.fun1()首先是運(yùn)行父類(lèi)Wine中的fun1().然后再運(yùn)行子類(lèi)JNC中的fun2()。
分析:在這個(gè)程序中子類(lèi)JNC重載了父類(lèi)Wine的方法fun1()克饶,重寫(xiě)fun2()酝蜒,而且重載后的fun1(String a)與 fun1()不是同一個(gè)方法,由于父類(lèi)中沒(méi)有該方法矾湃,向上轉(zhuǎn)型后會(huì)丟失該方法亡脑,所以執(zhí)行JNC的Wine類(lèi)型引用是不能引用fun1(String a)方法。而子類(lèi)JNC重寫(xiě)了fun2() 邀跃,那么指向JNC的Wine引用會(huì)調(diào)用JNC中fun2()方法霉咨。
所以對(duì)于多態(tài)我們可以總結(jié)如下:
指向子類(lèi)的父類(lèi)引用由于向上轉(zhuǎn)型了,它只能訪(fǎng)問(wèn)父類(lèi)中擁有的方法和屬性拍屑,而對(duì)于子類(lèi)中存在而父類(lèi)中不存在的方法途戒,該引用是不能使用的,盡管是重載該方法僵驰。若子類(lèi)重寫(xiě)了父類(lèi)中的某些方法喷斋,在調(diào)用該些方法的時(shí)候,必定是使用子類(lèi)中定義的這些方法(動(dòng)態(tài)連接蒜茴、動(dòng)態(tài)調(diào)用)星爪。
對(duì)于面向?qū)ο蠖眩鄳B(tài)分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)粉私。其中編輯時(shí)多態(tài)是靜態(tài)的顽腾,主要是指方法的重載,它是根據(jù)參數(shù)列表的不同來(lái)區(qū)分不同的函數(shù)毡鉴,通過(guò)編輯之后會(huì)變成兩個(gè)不同的函數(shù)崔泵,在運(yùn)行時(shí)談不上多態(tài)。而運(yùn)行時(shí)多態(tài)是動(dòng)態(tài)的猪瞬,它是通過(guò)動(dòng)態(tài)綁定來(lái)實(shí)現(xiàn)的憎瘸,也就是我們所說(shuō)的多態(tài)性。
多態(tài)的實(shí)現(xiàn)
2.1實(shí)現(xiàn)條件
在剛剛開(kāi)始就提到了繼承在為多態(tài)的實(shí)現(xiàn)做了準(zhǔn)備陈瘦。子類(lèi)Child繼承父類(lèi)Father幌甘,我們可以編寫(xiě)一個(gè)指向子類(lèi)的父類(lèi)類(lèi)型引用,該引用既可以處理父類(lèi)Father對(duì)象,也可以處理子類(lèi)Child對(duì)象锅风,當(dāng)相同的消息發(fā)送給子類(lèi)或者父類(lèi)對(duì)象時(shí)酥诽,該對(duì)象就會(huì)根據(jù)自己所屬的引用而執(zhí)行不同的行為,這就是多態(tài)皱埠。即多態(tài)性就是相同的消息使得不同的類(lèi)做出不同的響應(yīng)肮帐。
Java實(shí)現(xiàn)多態(tài)有三個(gè)必要條件:繼承、重寫(xiě)边器、向上轉(zhuǎn)型训枢。
繼承:在多態(tài)中必須存在有繼承關(guān)系的子類(lèi)和父類(lèi)。
重寫(xiě):子類(lèi)對(duì)父類(lèi)中某些方法進(jìn)行重新定義忘巧,在調(diào)用這些方法時(shí)就會(huì)調(diào)用子類(lèi)的方法恒界。
向上轉(zhuǎn)型:在多態(tài)中需要將子類(lèi)的引用賦給父類(lèi)對(duì)象,只有這樣該引用才能夠具備技能調(diào)用父類(lèi)的方法和子類(lèi)的方法砚嘴。
只有滿(mǎn)足了上述三個(gè)條件十酣,我們才能夠在同一個(gè)繼承結(jié)構(gòu)中使用統(tǒng)一的邏輯實(shí)現(xiàn)代碼處理不同的對(duì)象,從而達(dá)到執(zhí)行不同的行為际长。
對(duì)于Java而言耸采,它多態(tài)的實(shí)現(xiàn)機(jī)制遵循一個(gè)原則:當(dāng)超類(lèi)對(duì)象引用變量引用子類(lèi)對(duì)象時(shí),被引用對(duì)象的類(lèi)型而不是引用變量的類(lèi)型決定了調(diào)用誰(shuí)的成員方法工育,但是這個(gè)被調(diào)用的方法必須是在超類(lèi)中定義過(guò)的洋幻,也就是說(shuō)被子類(lèi)覆蓋的方法。
2.2實(shí)現(xiàn)形式
在Java中有兩種形式可以實(shí)現(xiàn)多態(tài)翅娶。繼承和接口。
2.2.1好唯、基于繼承實(shí)現(xiàn)的多態(tài)
基于繼承的實(shí)現(xiàn)機(jī)制主要表現(xiàn)在父類(lèi)和繼承該父類(lèi)的一個(gè)或多個(gè)子類(lèi)對(duì)某些方法的重寫(xiě)竭沫,多個(gè)子類(lèi)對(duì)同一方法的重寫(xiě)可以表現(xiàn)出不同的行為。
public class Wine {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name =name;
}
public Wine(){
}
public String drink(){
return "喝的是 " +getName();
}/*** 重寫(xiě)toString()*/
public String toString(){
return null;
}
}
public class JNC extends Wine{
public JNC(){
setName("JNC");
}/*** 重寫(xiě)父類(lèi)方法骑篙,實(shí)現(xiàn)多態(tài)*/
public String drink(){
return "喝的是 " + getName();
}/*** 重寫(xiě)toString()*/
public String toString(){
return "Wine : " +getName();
}
}
public class JGJ extends Wine{
public JGJ(){
setName("JGJ");
}/*** 重寫(xiě)父類(lèi)方法蜕提,實(shí)現(xiàn)多態(tài)*/
public String drink(){
return "喝的是 " +getName();
}/*** 重寫(xiě)toString()*/
public String toString(){
return "Wine : " +getName();
}
}
public class Test {
public static void main(String[] args) {
//定義父類(lèi)數(shù)組
Wine[] wines =new Wine[2];
//定義兩個(gè)子類(lèi)
JNC jnc = new JNC();
JGJ jgj = new JGJ();//父類(lèi)引用子類(lèi)對(duì)象wines[0] =jnc;
wines[1] = jgj ;
for(inti = 0 ; i < 2 ; i++){
System.out.println(wines[i].toString()+ "--" +wines[i].drink());
}
System.out.println("-------------------------------");
}
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ-------------------------------
在上面的代碼中JNC、JGJ繼承Wine靶端,并且重寫(xiě)了drink()谎势、toString()方法,程序運(yùn)行結(jié)果是調(diào)用子類(lèi)中方法杨名,輸出JNC脏榆、JGJ的名稱(chēng),這就是多態(tài)的表現(xiàn)台谍。不同的對(duì)象可以執(zhí)行相同的行為须喂,但是他們都需要通過(guò)自己的實(shí)現(xiàn)方式來(lái)執(zhí)行,這就要得益于向上轉(zhuǎn)型了。
我們都知道所有的類(lèi)都繼承自超類(lèi)Object坞生,toString()方法也是Object中方法仔役,當(dāng)我們這樣寫(xiě)時(shí):
Object o =new JGJ();
System.out.println(o.toString());
輸出的結(jié)果是Wine : JGJ。
Object是己、Wine又兵、JGJ三者繼承鏈關(guān)系是:JGJ—>Wine—>Object。所以我們可以這樣說(shuō):當(dāng)子類(lèi)重寫(xiě)父類(lèi)的方法被調(diào)用時(shí)卒废,只有對(duì)象繼承鏈中的最末端的方法才會(huì)被調(diào)用沛厨。但是注意如果這樣寫(xiě):
Object o =new Wine();
System.out.println(o.toString());
輸出的結(jié)果應(yīng)該是Null,因?yàn)镴GJ并不存在于該對(duì)象繼承鏈中升熊。
所以基于繼承實(shí)現(xiàn)的多態(tài)可以總結(jié)如下:對(duì)于引用子類(lèi)的父類(lèi)類(lèi)型俄烁,在處理該引用時(shí),它適用于繼承該父類(lèi)的所有子類(lèi)级野,子類(lèi)對(duì)象的不同页屠,對(duì)方法的實(shí)現(xiàn)也就不同,執(zhí)行相同動(dòng)作產(chǎn)生的行為也就不同蓖柔。
如果父類(lèi)是抽象類(lèi)辰企,那么子類(lèi)必須要實(shí)現(xiàn)父類(lèi)中所有的抽象方法,這樣該父類(lèi)所有的子類(lèi)一定存在統(tǒng)一的對(duì)外接口况鸣,但其內(nèi)部的具體實(shí)現(xiàn)可以各異牢贸。這樣我們就可以使用頂層類(lèi)提供的統(tǒng)一接口來(lái)處理該層次的方法。
2.2.2镐捧、基于接口實(shí)現(xiàn)的多態(tài)
繼承是通過(guò)重寫(xiě)父類(lèi)的同一方法的幾個(gè)不同子類(lèi)來(lái)體現(xiàn)的潜索,那么就可就是通過(guò)實(shí)現(xiàn)接口并覆蓋接口中同一方法的幾不同的類(lèi)體現(xiàn)的。
在接口的多態(tài)中懂酱,指向接口的引用必須是指定這實(shí)現(xiàn)了該接口的一個(gè)類(lèi)的實(shí)例程序竹习,在運(yùn)行時(shí),根據(jù)對(duì)象引用的實(shí)際類(lèi)型來(lái)執(zhí)行對(duì)應(yīng)的方法列牺。
繼承都是單繼承整陌,只能為一組相關(guān)的類(lèi)提供一致的服務(wù)接口。但是接口可以是多繼承多實(shí)現(xiàn)瞎领,它能夠利用一組相關(guān)或者不相關(guān)的接口進(jìn)行組合與擴(kuò)充泌辫,能夠?qū)ν馓峁┮恢碌姆?wù)接口。所以它相對(duì)于繼承來(lái)說(shuō)有更好的靈活性九默。