最近在復(fù)習(xí)java基礎(chǔ)瘫寝,在多態(tài)上面有一些不太明白的地方,故記錄下來供自己時(shí)時(shí)復(fù)習(xí)查閱鞏固稠炬。
概述
我們都知道面向?qū)ο笥腥筇匦裕?b>封裝焕阿,繼承,多態(tài)首启。封裝很好理解暮屡,就是對于數(shù)據(jù)的保護(hù)機(jī)制,對外部隱藏內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)毅桃,僅僅暴露訪問方法褒纲,這樣的好處在于對于業(yè)務(wù)來說不用關(guān)心實(shí)現(xiàn)細(xì)節(jié),而當(dāng)實(shí)現(xiàn)細(xì)節(jié)需要改變時(shí)钥飞,也不會影響外部業(yè)務(wù)外厂,可以說,封裝是解耦的基礎(chǔ)代承,也是面向?qū)ο笏枷氲目s影汁蝶。
繼承和多態(tài)往往會把它們放在一起講,繼承是為了重用父類并對父類進(jìn)行擴(kuò)展论悴,正式因?yàn)橛辛死^承掖棉,才導(dǎo)致了多態(tài)的出現(xiàn),那么多態(tài)到底定義是什么呢膀估?所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定幔亥,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會指向哪個(gè)類的實(shí)例對象察纯,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法帕棉,必須在由程序運(yùn)行期間才能決定。因?yàn)樵诔绦蜻\(yùn)行時(shí)才確定具體的類饼记,這樣香伴,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實(shí)現(xiàn)上具则,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變即纲,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài)博肋,這就是多態(tài)性低斋。
什么意思呢蜂厅?比如有一個(gè)汽車類Car,車庫里面有奔馳Benz膊畴、寶馬BMW掘猿、比亞迪BYD,那么可以有如下構(gòu)建方法
Car a = new Benz();
Car b = new BMW();
Car c = new BYD();
我們事前并不知道這輛車是什么車唇跨,只有當(dāng)它開出來我們才能知道术奖,這就是多態(tài)——我們只有在運(yùn)行的時(shí)候才會知道引用變量所指向的具體實(shí)例對象。
向上轉(zhuǎn)型
要理解多態(tài)就要知道什么叫向上轉(zhuǎn)型轻绞,在上述例子中,如果只是像如下創(chuàng)建
Benz a = new Benz();
那就只是一個(gè)單純的實(shí)例化了一個(gè)Benz對象佣耐,但是如果是這樣呢政勃?
Car a = new Benz();
我們可以看到,我們定義了一個(gè)Car的引用兼砖,指向了Benz實(shí)例對象奸远,可以這樣的原因是Benz繼承于Car,所以Benz可以轉(zhuǎn)型為Car讽挟,這個(gè)過程就叫做向上轉(zhuǎn)型懒叛。這樣做存在一個(gè)非常大的好處,在繼承中我們知道子類是父類的擴(kuò)展耽梅,它可以提供比父類更加強(qiáng)大的功能薛窥,如果我們定義了一個(gè)指向子類的父類引用類型,那么它除了能夠引用父類的共性外眼姐,還可以使用子類強(qiáng)大的功能诅迷。
但是向上轉(zhuǎn)型存在一些缺憾,那就是它必定會導(dǎo)致一些方法和屬性的丟失众旗,而導(dǎo)致我們不能夠獲取它們罢杉。所以父類類型的引用可以調(diào)用父類中定義的所有屬性和方法,對于只存在與子類中的方法和屬性無法調(dià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 子類重載父類方法
? ? *? ? ? ? 父類中不存在該方法滩租,向上轉(zhuǎn)型后,父類是不能引用該方法的
? ? * @param a
? ? * @return void
? ? */
? ? public void fun1(String a){
? ? ? ? System.out.println("JNC 的 Fun1...");
? ? ? ? fun2();
? ? }
? ? /**
? ? * 子類重寫父類方法
? ? * 指向子類的父類引用調(diào)用fun2時(shí)利朵,必定是調(diào)用該方法
? ? */
? ? public void fun2(){
? ? ? ? System.out.println("JNC 的Fun2...");
? ? }
}
public class Test {
? ? public static void main(String[] args) {
? ? ? ? Wine a = new JNC();
? ? ? ? a.fun1();
? ? }
}
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...
分析:在這個(gè)程序中子類JNC重載了父類Wine的方法fun1()律想,重寫fun2(),而且重載后的fun1(String a)與 fun1()不是同一個(gè)方法绍弟,由于父類中沒有該方法蜘欲,向上轉(zhuǎn)型后會丟失該方法,所以執(zhí)行JNC的Wine類型引用是不能引用fun1(String a)方法晌柬。而子類JNC重寫了fun2() 姥份,那么指向JNC的Wine引用會調(diào)用JNC中fun2()方法郭脂。
?所以對于多態(tài)我們可以總結(jié)如下:
多態(tài)的必要條件有3個(gè),繼承澈歉,重寫展鸡,向上轉(zhuǎn)型。指向子類的父類引用由于向上轉(zhuǎn)型了埃难,它只能訪問父類中擁有的方法和屬性莹弊,而對于子類中存在而父類中不存在的方法,該引用是不能使用的涡尘,盡管是重載該方法忍弛。若子類重寫了父類中的某些方法,在調(diào)用該些方法的時(shí)候考抄,必定是使用子類中定義的這些方法(動態(tài)連接细疚、動態(tài)調(diào)用)。
再看一個(gè)經(jīng)典例子
public class A {
? ? public String show(D obj) {
? ? ? ? return ("A and D");
? ? }
? ? public String show(A obj) {
? ? ? ? return ("A and A");
? ? }
}
public class B extends A{
? ? public String show(B obj){
? ? ? ? return ("B and B");
? ? }
? ? public String show(A obj){
? ? ? ? return ("B and A");
? ? }
}
public class C extends B{
}
public class D extends B{
}
public class Test {
? ? public static void main(String[] args) {
? ? ? ? A a1 = new A();
? ? ? ? A a2 = new B();
? ? ? ? B b = new B();
? ? ? ? C c = new C();
? ? ? ? D d = new D();
? ? ? ? System.out.println("1--" + a1.show(b));
? ? ? ? System.out.println("2--" + a1.show(c));
? ? ? ? System.out.println("3--" + a1.show(d));
? ? ? ? System.out.println("4--" + a2.show(b));
? ? ? ? System.out.println("5--" + a2.show(c));
? ? ? ? System.out.println("6--" + a2.show(d));
? ? ? ? System.out.println("7--" + b.show(b));
? ? ? ? System.out.println("8--" + b.show(c));
? ? ? ? System.out.println("9--" + b.show(d));? ? ?
? ? }
}
結(jié)果為
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
分析一下:
1.?a1.show(b)川梅,由于B extends A,所以真正調(diào)用的是A.show(A)
2.a1.show(c)疯兼,由于C extends B,B extends A,所以真正調(diào)用的是A.show(A)
3.a1.show(d)贫途,直接調(diào)用A.show(D)
4.a2.show(b)吧彪,可能這里會疑惑為什么不是B and B,原因在于雖然B繼承了A丢早,但是父類A中其實(shí)是沒有show(B)這個(gè)方法的姨裸,上面有說到,?指向子類的父類引用由于向上轉(zhuǎn)型了怨酝,它只能訪問父類中擁有的方法和屬性啦扬,而對于子類中存在而父類中不存在的方法,該引用是不能使用的凫碌,所以最后是調(diào)用的B類的show(A)方法扑毡,因?yàn)锽繼承A。
5.a2.show(c)盛险,同4理瞄摊。
6.a2.show(d),父類中存在show(D)方法苦掘,而子類B并沒有重寫换帜,所以直接調(diào)用A.show(D)
7.?b.show(b),b是創(chuàng)建的B對象鹤啡,也是B的引用惯驼,不存在向上轉(zhuǎn)型,所以直接調(diào)用B.show(B)
8.b.show(c),父類A中沒有show(C)方法祟牲,所以會調(diào)用子類隙畜,又由于C繼承于B,B繼承于A说贝,在調(diào)用中议惰,會調(diào)用繼承關(guān)系最近的那一個(gè),所以直接調(diào)用B.show(B)
9.b.show(d)乡恕,父類A中有方法show(D)言询,而子類B中并沒有重寫,所以直接調(diào)用A.show(D)
通過上面例子可以發(fā)現(xiàn)一個(gè)事實(shí)傲宜,在繼承鏈中對象方法的調(diào)用存在一個(gè)優(yōu)先級:this.show(O)运杭、super.show(O)、this.show((super)O)函卒、super.show((super)O)辆憔。在例子的5中,a2.show(c)谆趾,a2是A類型的引用變量,所以this就代表了A叛本,a2.show(c),它在A類中找發(fā)現(xiàn)沒有找到沪蓬,于是到A的超類中找(super),由于A沒有超類(Object除外)来候,所以跳到第三級跷叉,也就是this.show((super)O),C的超類有B营搅、A云挟,所以(super)O為B、A转质,this同樣是A园欣,這里在A中找到了show(A obj),同時(shí)由于a2是B類的一個(gè)引用且B類重寫了show(A obj)休蟹,因此最終會調(diào)用子類B類的show(A obj)方法沸枯,結(jié)果也就是B and A。