java提高篇四_(转)java提高篇(四)-----理解java的三大特性之多态
面向對象編程有三大特性:封裝、繼承、多態。
封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。
繼承是為了重用父類代碼。兩個類若存在IS-A的關系就可以使用繼承。,同時繼承也為實現多態做了鋪墊。那么什么是多態呢?多態的實現機制又是什么?請看我一一為你揭開:
所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。
比如你是一個酒神,對酒情有獨鐘。某日回家發現桌上有幾個杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒,只有喝了之后才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:
酒 a = 劍南春
酒 b = 五糧液
酒 c = 酒鬼酒
…
這里所表現的的就是多態。劍南春、五糧液、酒鬼酒都是酒的子類,我們只是通過酒這一個父類就能夠引用不同的子類,這就是多態——我們只有在運行的時候才會知道引用變量所指向的具體實例對象。
誠然,要理解多態我們就必須要明白什么是“向上轉型”。在繼承中我們簡單介紹了向上轉型,這里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:
JNC a = new? JNC();
對于這個代碼我們非常容易理解無非就是實例化了一個劍南春的對象嘛!但是這樣呢?
Wine a = new JNC();
在這里我們這樣理解,這里定義了一個Wine 類型的a,它指向JNC對象實例。由于JNC是繼承與Wine,所以JNC可以自動向上轉型為Wine,所以a是可以指向JNC實例對象的。這樣做存在一個非常大的好處,在繼承中我們知道子類是父類的擴展,它可以提供比父類更加強大的功能,如果我們定義了一個指向子類的父類引用類型,那么它除了能夠引用父類的共性外,還可以使用子類強大的功能。
但是向上轉型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調用父類中定義的所有屬性和方法,對于只存在與子類中的方法和屬性它就望塵莫及了---1。
public classWine {public voidfun1(){
System.out.println("Wine 的Fun.....");
fun2();
}public voidfun2(){
System.out.println("Wine 的Fun2...");
}
}public class JNC extendsWine{/*** @desc 子類重載父類方法
* 父類中不存在該方法,向上轉型后,父類是不能引用該方法的
*@parama
*@returnvoid*/
public voidfun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}/*** 子類重寫父類方法
* 指向子類的父類引用調用fun2時,必定是調用該方法*/
public voidfun2(){
System.out.println("JNC 的Fun2...");
}
}public classTest {public static voidmain(String[] args) {
Wine a= newJNC();
a.fun1();
}
}-------------------------------------------------Output:
Wine 的Fun.....
JNC 的Fun2...
從程序的運行結果中我們發現,a.fun1()首先是運行父類Wine中的fun1().然后再運行子類JNC中的fun2()。
分析:在這個程序中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),而且重載后的fun1(String a)與 fun1()不是同一個方法,由于父類中沒有該方法,向上轉型后會丟失該方法,所以執行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那么指向JNC的Wine引用會調用JNC中fun2()方法。
所以對于多態我們可以總結如下:
指向子類的父類引用由于向上轉型了,它只能訪問父類中擁有的方法和屬性,而對于子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態連接、動態調用)。
對于面向對象而已,多態分為編譯時多態和運行時多態。其中編譯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的函數,通過編輯之后會變成兩個不同的函數,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是我們所說的多態性。
多態的實現
2.1實現條件
在剛剛開始就提到了繼承在為多態的實現做了準備。子類Child繼承父類Father,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類Father對象,也可以處理子類Child對象,當相同的消息發送給子類或者父類對象時,該對象就會根據自己所屬的引用而執行不同的行為,這就是多態。即多態性就是相同的消息使得不同的類做出不同的響應。
Java實現多態有三個必要條件:繼承、重寫、向上轉型。
繼承:在多態中必須存在有繼承關系的子類和父類。
重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。
只有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統一的邏輯實現代碼處理不同的對象,從而達到執行不同的行為。
對于Java而言,多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
2.2實現形式
在Java中有兩種形式可以實現多態。繼承和接口。
2.2.1、基于繼承實現的多態
基于繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行為。
public classWine {privateString name;publicString getName() {returnname;
}public voidsetName(String name) {this.name =name;
}publicWine(){
}publicString drink(){return "喝的是 " +getName();
}/*** 重寫toString()*/
publicString toString(){return null;
}
}public class JNC extendsWine{publicJNC(){
setName("JNC");
}/*** 重寫父類方法,實現多態*/
publicString drink(){return "喝的是 " +getName();
}/*** 重寫toString()*/
publicString toString(){return "Wine : " +getName();
}
}public class JGJ extendsWine{publicJGJ(){
setName("JGJ");
}/*** 重寫父類方法,實現多態*/
publicString drink(){return "喝的是 " +getName();
}/*** 重寫toString()*/
publicString toString(){return "Wine : " +getName();
}
}public classTest {public static voidmain(String[] args) {//定義父類數組
Wine[] wines = new Wine[2];//定義兩個子類
JNC jnc = newJNC();
JGJ jgj= newJGJ();//父類引用子類對象
wines[0] =jnc;
wines[1] =jgj;for(int i = 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,并且重寫了drink()、toString()方法,程序運行結果是調用子類中方法,輸出JNC、JGJ的名稱,這就是多態的表現。不同的對象可以執行相同的行為,但是他們都需要通過自己的實現方式來執行,這就要得益于向上轉型了。
我們都知道所有的類都繼承自超類Object,toString()方法也是Object中方法,當我們這樣寫時:
Object o = newJGJ();
System.out.println(o.toString());
輸出的結果是Wine : JGJ。
Object、Wine、JGJ三者繼承鏈關系是:JGJ—>Wine—>Object。所以我們可以這樣說:當子類重寫父類的方法被調用時,只有對象繼承鏈中的最末端的方法才會被調用。但是注意如果這樣寫:
Object o = newWine();
System.out.println(o.toString());
輸出的結果應該是Null,因為JGJ并不存在于該對象繼承鏈中。
所以基于繼承實現的多態可以總結如下:對于引用子類的父類類型,在處理該引用時,它適用于繼承該父類的所有子類,子類對象的不同,對方法的實現也就不同,執行相同動作產生的行為也就不同。
如果父類是抽象類,那么子類必須要實現父類中所有的抽象方法,這樣該父類所有的子類一定存在統一的對外接口,但其內部的具體實現可以各異。這樣我們就可以使用頂層類提供的統一接口來處理該層次的方法。
2.2.2、基于接口實現的多態
繼承是通過重寫父類的同一方法的幾個不同子類來體現的,那么就可就是通過實現接口并覆蓋接口中同一方法的幾不同的類體現的。
在接口的多態中,指向接口的引用必須是指定這實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。
繼承都是單繼承,只能為一組相關的類提供一致的服務接口。但是接口可以是多繼承多實現,它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一致的服務接口。所以它相對于繼承來說有更好的靈活性。
經典實例
public classA {publicString show(D obj) {return ("A and D");
}publicString show(A obj) {return ("A and A");
}
}public class B extendsA{publicString show(B obj){return ("B and B");
}publicString show(A obj){return ("B and A");
}
}public class C extendsB{
}public class D extendsB{
}public classTest {public static voidmain(String[] args) {
A a1= newA();
A a2= newB();
B b= newB();
C c= newC();
D d= newD();
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));
}
}
運行結果:
1--A and A2--A and A3--A and D4--B and A5--B and A6--A and D7--B and B8--B and B9--A and D
在這里看結果1、2、3還好理解,從4開始就開始糊涂了,對于4來說為什么輸出不是“B and B”呢?
首先我們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這句話對多態進行了一個概括。其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
分析:
從上面的程序中我們可以看出A、B、C、D存在如下關系。
首先我們分析5,a2.show(c),a2是A類型的引用變量,所以this就代表了A,a2.show(c),它在A類中找發現沒有找到,于是到A的超類中找(super),由于A沒有超類(Object除外),所以跳到第三級,也就是this.show((super)O),C的超類有B、A,所以(super)O為B、A,this同樣是A,這里在A中找到了show(A obj),同時由于a2是B類的一個引用且B類重寫了show(A obj),因此最終會調用子類B類的show(A obj)方法,結果也就是B and A。
按照同樣的方法我也可以確認其他的答案。
方法已經找到了但是我們這里還是存在一點疑問,我們還是來看這句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這我們用一個例子來說明這句話所代表的含義:a2.show(b);
這里a2是引用變量,為A類型,它引用的是B對象,因此按照上面那句話的意思是說有B來決定調用誰的方法,所以a2.show(b)應該要調用B中的show(B obj),產生的結果應該是“B and B”,但是為什么會與前面的運行結果產生差異呢?這里我們忽略了后面那句話“但是這兒被調用的方法必須是在超類中定義過的”,那么show(B obj)在A類中存在嗎?根本就不存在!所以這句話在這里不適用?那么難道是這句話錯誤了?非也!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調用方法的優先級來確認。所以它才會在A類中找到show(A obj),同時由于B重寫了該方法所以才會調用B類中的方法,否則就會調用A類中的方法。
所以多態機制遵循的原則概括為:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,但是它仍然要根據繼承鏈中方法調用的優先級來確認方法,該優先級為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
對最后的一個例子補充。深入理解多態的性質(上下例子其實一樣,上面的藍色部分是總結)
深入理解java多態性
昨天看到一個關于多態性的帖子,參考了回帖者的理解,加入了一些自己的看法,整理出來供大家參考,不一定完全正確,歡迎大家批評指正。
(一)相關類
classA...{
publicString?show(D?obj)...{
return("A?and?D");
?????????}
publicString?show(A?obj)...{
return("A?and?A");
?????????}}
classBextendsA...{
publicString?show(B?obj)...{
return("B?and?B");
?????????}
publicString?show(A?obj)...{
return("B?and?A");
?????????}}
classCextendsB...{}
classDextendsB...{}
(二)問題:以下輸出結果是什么?
????????A?a1=newA();
????????A?a2=newB();
????????B?b?=newB();
????????C?c=newC();?
????????D?d=newD();?
System.out.println(a1.show(b));?? ①
????????System.out.println(a1.show(c));?? ②
????????System.out.println(a1.show(d));?? ③
????????System.out.println(a2.show(b));?? ④
????????System.out.println(a2.show(c));?? ⑤
????????System.out.println(a2.show(d));?? ⑥
????????System.out.println(b.show(b));???? ⑦
????????System.out.println(b.show(c));?????⑧
????????System.out.println(b.show(d));???? ⑨
(三)答案
①?? A?and A
②?? A and A
③?? A and D
④?? B and A
⑤?? B and A
⑥?? A and D
⑦?? B and B
⑧?? B and B
⑨?? A and D
(四)分析
①②③比較好理解,一般不會出錯。④⑤就有點糊涂了,為什么輸出的不是"B and B”呢?!!先來回顧一下多態性。
運行時多態性是面向對象程序設計代碼重用的一個最強大機制,動態性的概念也可以被說成“一個接口,多個方法”。Java實現運行時多態性的基礎是動態方法調度,它是一種在運行時而不是在編譯期調用重載方法的機制。
方法的重寫Overriding和重載Overloading是Java多態性的不同表現。重寫Overriding是父類與子類之間多態性的一種表現,重載Overloading是一個類中多態性的一種表現。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫(Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被“屏蔽”了。如果在一個類中定義了多個同名的方法,它們或有不同的參數個數或有不同的參數類型,則稱為方法的重載(Overloading)。Overloaded的方法是可以改變返回值的類型。
當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。?(但是如果強制把超類轉換成子類的話,就可以調用子類中新添加而超類沒有的方法了。)
好了,先溫習到這里,言歸正傳!實際上這里涉及方法調用的優先問題 ,優先級由高到低依次為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。這里的this指引用變量所在的類。讓我們來看看它是怎么工作的。
注意點:如果this指向的是基類的引用,那么對于this.show(O)、this.show((super)O),如果在this所在的類中找到對應的方法時候,還要繼續判斷子類中是否重寫該方法(
子類.show(O)、子類.show((super)O)),如果實現重寫覆蓋,那么最終的執行就鎖定在子類的方法中,否則就執行基類中的相關方法(
基類.show(O)、基類.show((super)O))
比如④,a2.show(b),a2是一個引用變量,類型為A,則this為a2,b是B的一個實例,于是它到類A里面找show(B obj)方法,沒有找到,于是到A的super(超類)找,而A沒有超類,因此轉到第三優先級this.show((super)O),this仍然是a2,這里O為B,(super)O即(super)B即A,因此它到類A里面找show(A obj)的方法,類A有這個方法,但是由于a2引用的是類B的一個對象,B覆蓋了A的show(A
obj)方法,因此最終鎖定到類B的show(A obj),輸出為"B and A”。
再比如⑧,b.show(c),b是一個引用變量,類型為B,則this為b,c是C的一個實例,于是它到類B找show(C obj)方法,沒有找到,轉而到B的超類A里面找,A里面也沒有,因此也轉到第三優先級this.show((super)O),this為b,O為C,(super)O即(super)C即B,因此它到B里面找show(B obj)方法,找到了,由于b引用的是類B的一個對象,因此直接鎖定到類B的show(B obj),輸出為"B
and B”。
按照上面的方法,可以正確得到其他的結果。
問題還要繼續,現在我們再來看上面的分析過程是怎么體現出藍色字體那句話的內涵的。它說:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。還是拿a2.show(b)來說吧。
a2是一個引用變量,類型為A,它引用的是B的一個對象,因此這句話的意思是由B來決定調用的是哪個方法。因此應該調用B的show(B obj)從而輸出"B and B”才對。但是為什么跟前面的分析得到的結果不相符呢?!問題在于我們不要忽略了藍色字體的后半部分,那里特別指明:這個被調用的方法必須是在超類中定義過的,也就是被子類覆蓋的方法。B里面的show(B obj)在超類A中有定義嗎?沒有!那就更談不上被覆蓋了。實際上這句話隱藏了一條信息:它仍然是按照方法調用的優先級來確定的。它在類A中找到了show(A
obj),如果子類B沒有覆蓋show(A obj)方法,那么它就調用A的show(A obj)(由于B繼承A,雖然沒有覆蓋這個方法,但從超類A那里繼承了這個方法,從某種意義上說,還是由B確定調用的方法,只是方法是在A中實現而已);現在子類B覆蓋了show(A obj),因此它最終鎖定到B的show(A obj)。這就是那句話的意義所在。
在這里面向對象的三大特性已經介紹完成,下一步繼續是java基礎部分—鞏固基礎,提高技術,不懼困難,攀登高峰!!!!!!
總結
以上是生活随笔為你收集整理的java提高篇四_(转)java提高篇(四)-----理解java的三大特性之多态的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue 文字转语音mp3_vue项目或网
- 下一篇: com 组件调用不起来_AwesomeG