欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Java三大特性中的多態(tài)

 更新時間:2017年01月19日 09:15:17   作者:Java開發(fā)-擱淺  
多態(tài)性是對象多種表現(xiàn)形式的體現(xiàn)。在面向對象中,最常見的多態(tài)發(fā)生在使用父類的引用來引用子類的對象。下面這篇文章主要給大家深入的介紹了Java三大特性中多態(tài)的相關資料,有需要的朋友可以參考借鑒,下面來一起看看吧。

Java三大特性

面向對象編程有三大特性:封裝、繼承、多態(tài)。

封裝隱藏了類的內部實現(xiàn)機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節(jié)是隱藏的,暴露給外界的只是它的訪問方法。

繼承是為了重用父類代碼。兩個類若存在IS-A的關系就可以使用繼承。,同時繼承也為實現(xiàn)多態(tài)做了鋪墊。那么什么是多態(tài)呢?多態(tài)的實現(xiàn)機制又是什么?請看我一一為你揭開:

所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發(fā)出的方法調用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態(tài),這就是多態(tài)性。

比如你是一個酒神,對酒情有獨鐘。某日回家發(fā)現(xiàn)桌上有幾個杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒,只有喝了之后才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:

酒 a = 劍南春

酒 b = 五糧液

酒 c = 酒鬼酒

這里所表現(xiàn)的的就是多態(tài)。劍南春、五糧液、酒鬼酒都是酒的子類,我們只是通過酒這一個父類就能夠引用不同的子類,這就是多態(tài)——我們只有在運行的時候才會知道引用變量所指向的具體實例對象。

誠然,要理解多態(tài)我們就必須要明白什么是“向上轉型”。在繼承中我們簡單介紹了向上轉型,這里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:

JNC a = new  JNC();

對于這個代碼我們非常容易理解無非就是實例化了一個劍南春的對象嘛!但是這樣呢?

Wine a = new JNC();

在這里我們這樣理解,這里定義了一個Wine 類型的a,它指向JNC對象實例。由于JNC是繼承與Wine,所以JNC可以自動向上轉型為Wine,所以a是可以指向JNC實例對象的。這樣做存在一個非常大的好處,在繼承中我們知道子類是父類的擴展,它可以提供比父類更加強大的功能,如果我們定義了一個指向子類的父類引用類型,那么它除了能夠引用父類的共性外,還可以使用子類強大的功能。

但是向上轉型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調用父類中定義的所有屬性和方法,對于只存在與子類中的方法和屬性它就望塵莫及了---1

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 子類重載父類方法
  *  父類中不存在該方法,向上轉型后,父類是不能引用該方法的
  * @param a
  * @return void
  */
 public void fun1(String a){
  System.out.println("JNC 的 Fun1...");
  fun2();
 }
 
 /**
  * 子類重寫父類方法
  * 指向子類的父類引用調用fun2時,必定是調用該方法
  */
 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...

從程序的運行結果中我們發(fā)現(xiàn),a.fun1()首先是運行父類Wine中的fun1() .然后再運行子類JNC中的fun2() 。

分析:在這個程序中子類JNC重載了父類Wine的方法fun1() ,重寫fun2() ,而且重載后的fun1(String a)fun1()不是同一個方法,由于父類中沒有該方法,向上轉型后會丟失該方法,所以執(zhí)行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那么指向JNC的Wine引用會調用JNC中fun2()方法。

所以對于多態(tài)我們可以總結如下:

指向子類的父類引用由于向上轉型了,它只能訪問父類中擁有的方法和屬性,而對于子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態(tài)連接、動態(tài)調用)。

對于面向對象而已,多態(tài)分為編譯時多態(tài)和運行時多態(tài)。其中編輯時多態(tài)是靜態(tài)的,主要是指方法的重載,它是根據參數列表的不同來區(qū)分不同的函數,通過編輯之后會變成兩個不同的函數,在運行時談不上多態(tài)。而運行時多態(tài)是動態(tài)的,它是通過動態(tài)綁定來實現(xiàn)的,也就是我們所說的多態(tài)性。

多態(tài)的實現(xiàn)

2.1實現(xiàn)條件

在剛剛開始就提到了繼承在為多態(tài)的實現(xiàn)做了準備。子類Child繼承父類Father,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類Father對象,也可以處理子類Child對象,當相同的消息發(fā)送給子類或者父類對象時,該對象就會根據自己所屬的引用而執(zhí)行不同的行為,這就是多態(tài)。即多態(tài)性就是相同的消息使得不同的類做出不同的響應。

Java實現(xiàn)多態(tài)有三個必要條件:繼承、重寫、向上轉型。

      繼承:在多態(tài)中必須存在有繼承關系的子類和父類。

      重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。

      向上轉型:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。

只有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統(tǒng)一的邏輯實現(xiàn)代碼處理不同的對象,從而達到執(zhí)行不同的行為。

對于Java而言,它多態(tài)的實現(xiàn)機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。

2.2實現(xiàn)形式

在Java中有兩種形式可以實現(xiàn)多態(tài)。繼承和接口。

2.2.1、基于繼承實現(xiàn)的多態(tài)

基于繼承的實現(xiàn)機制主要表現(xiàn)在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現(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();
 }
 
 /**
  * 重寫toString()
  */
 public String toString(){
  return null;
 }
}

public class JNC extends Wine{
 public JNC(){
  setName("JNC");
 }
 
 /**
  * 重寫父類方法,實現(xiàn)多態(tài)
  */
 public String drink(){
  return "喝的是 " + getName();
 }
 
 /**
  * 重寫toString()
  */
 public String toString(){
  return "Wine : " + getName();
 }
}

public class JGJ extends Wine{
 public JGJ(){
  setName("JGJ");
 }
 
 /**
  * 重寫父類方法,實現(xiàn)多態(tài)
  */
 public String drink(){
  return "喝的是 " + getName();
 }
 
 /**
  * 重寫toString()
  */
 public String toString(){
  return "Wine : " + getName();
 }
}

public class Test {
 public static void main(String[] args) {
  //定義父類數組
  Wine[] wines = new Wine[2];
  //定義兩個子類
  JNC jnc = new JNC();
  JGJ jgj = new JGJ();
  
  //父類引用子類對象
  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的名稱,這就是多態(tài)的表現(xiàn)。不同的對象可以執(zhí)行相同的行為,但是他們都需要通過自己的實現(xiàn)方式來執(zhí)行,這就要得益于向上轉型了。

我們都知道所有的類都繼承自超類Object,toString()方法也是Object中方法,當我們這樣寫時:

Object o = new JGJ();

  System.out.println(o.toString());

 輸出的結果是Wine : JGJ。

Object、Wine、JGJ三者繼承鏈關系是:JGJ—>Wine—>Object。所以我們可以這樣說:當子類重寫父類的方法被調用時,只有對象繼承鏈中的最末端的方法才會被調用

但是注意如果這樣寫:

Object o = new Wine();

System.out.println(o.toString());

輸出的結果應該是Null,因為JGJ并不存在于該對象繼承鏈中。

所以基于繼承實現(xiàn)的多態(tài)可以總結如下:對于引用子類的父類類型,在處理該引用時,它適用于繼承該父類的所有子類,子類對象的不同,對方法的實現(xiàn)也就不同,執(zhí)行相同動作產生的行為也就不同。

如果父類是抽象類,那么子類必須要實現(xiàn)父類中所有的抽象方法,這樣該父類所有的子類一定存在統(tǒng)一的對外接口,但其內部的具體實現(xiàn)可以各異。這樣我們就可以使用頂層類提供的統(tǒng)一接口來處理該層次的方法。

2.2.2、基于接口實現(xiàn)的多態(tài)

繼承是通過重寫父類的同一方法的幾個不同子類來體現(xiàn)的,那么就可就是通過實現(xiàn)接口并覆蓋接口中同一方法的幾不同的類體現(xiàn)的。

在接口的多態(tài)中,指向接口的引用必須是指定這實現(xiàn)了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執(zhí)行對應的方法。

繼承都是單繼承,只能為一組相關的類提供一致的服務接口。但是接口可以是多繼承多實現(xiàn),它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一致的服務接口。所以它相對于繼承來說有更好的靈活性。

三、經典實例。

通過上面的講述,可以說是對多態(tài)有了一定的了解?,F(xiàn)在趁熱打鐵,看一個實例。該實例是有關多態(tài)的經典例子,摘自:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。

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));  
 }
}

運行結果:

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、2、3還好理解,從4開始就開始糊涂了,對于4來說為什么輸出不是“B and B”呢?

首先我們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這句話對多態(tài)進行了一個概括。其實在繼承鏈中對象方法的調用存在一個優(yōu)先級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類中找發(fā)現(xiàn)沒有找到,于是到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類中存在嗎?根本就不存在!所以這句話在這里不適用?那么難道是這句話錯誤了?非也!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調用方法的優(yōu)先級來確認。所以它才會在A類中找到show(A obj) ,同時由于B重寫了該方法所以才會調用B類中的方法,否則就會調用A類中的方法。

所以多態(tài)機制遵循的原則概括為:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,但是它仍然要根據繼承鏈中方法調用的優(yōu)先級來確認方法,該優(yōu)先級為this.show(O) 、super.show(O) 、this.show((super)O) 、super.show((super)O) 。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關文章

  • spring中FactoryBean中的getObject()方法實例解析

    spring中FactoryBean中的getObject()方法實例解析

    這篇文章主要介紹了spring中FactoryBean中的getObject()方法實例解析,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • java中調用https請求忽略ssl證書認證代碼示例

    java中調用https請求忽略ssl證書認證代碼示例

    在網絡請求中經常會遇到需要忽略證書認證的情況,這篇文章主要介紹了java中調用https請求忽略ssl證書認證的相關資料,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • SpringSecurity的防Csrf攻擊實現(xiàn)代碼解析

    SpringSecurity的防Csrf攻擊實現(xiàn)代碼解析

    這篇文章主要介紹了SpringSecurity的防Csrf攻擊實現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • Java中的引用類型和使用場景詳細

    Java中的引用類型和使用場景詳細

    這篇文章介紹的是Java中的引用類型和使用場景,主要內容展開Java中的引用類型,有強引用、軟引用 、弱引用、虛引用,需要的朋友可以參考一下
    2021-10-10
  • 詳解Java如何關閉線程以及線程池

    詳解Java如何關閉線程以及線程池

    java如何正確關閉線程以及線程池是一個高頻的面試題,本文將為大家詳細介紹實現(xiàn)的方法與代碼,感興趣的小伙伴快跟隨小編一起學習一下
    2022-04-04
  • SpringCloud中Eureka的配置及使用講解

    SpringCloud中Eureka的配置及使用講解

    Eureka?服務注冊中心,主要用于提供服務注冊功能,當微服務啟動時,會將自己的服務注冊到?Eureka?Server,這篇文章主要介紹了SpringCloud中Eureka的配置及詳細使用,需要的朋友可以參考下
    2023-01-01
  • Java程序員面試中的多線程問題總結

    Java程序員面試中的多線程問題總結

    這篇文章收集了 Java 線程方面一些典型的問題,這些問題經常被高級工程師所問到。這篇文章收集了 Java 線程方面一些典型的問題,這些問題經常被高級工程師所問到。
    2016-11-11
  • Java 處理圖片與base64 編碼的相互轉換的示例

    Java 處理圖片與base64 編碼的相互轉換的示例

    本篇文章主要介紹了Java 處理圖片與base64 編碼的相互轉換的示例,具有一定的參考價值,有興趣的可以了解一下
    2017-08-08
  • Mybatis-Plus主鍵插入null值報錯問題及解決

    Mybatis-Plus主鍵插入null值報錯問題及解決

    這篇文章主要介紹了Mybatis-Plus主鍵插入null值報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 詳解IDEA自定義注釋模板(javadoc)

    詳解IDEA自定義注釋模板(javadoc)

    這篇文章主要介紹了詳解IDEA自定義注釋模板(javadoc),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04

最新評論