夯實Java基礎(四)——面向對象之多態

来源:https://www.cnblogs.com/tang-hao-/archive/2019/07/19/11213275.html
-Advertisement-
Play Games

1、多態介紹 面向對象三大特征:封裝、繼承、多態。多態是Java面向對象最核心,最難以理解的內容。從一定角度來看,封裝和繼承幾乎都是為多態而準備的。 多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編譯時並不確定,而是在程式運行期間才確定,即一個引用變數倒底會指向哪個類 ...


1、多態介紹

面向對象三大特征:封裝、繼承、多態。多態是Java面向對象最核心,最難以理解的內容。從一定角度來看,封裝和繼承幾乎都是為多態而準備的。

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

比如有一種動物,藏在某個看不見得地方,你知道它是一種動物,但是不知道具體是哪種動物,只有等它發出叫聲才能辨別,是貓——喵喵喵,是狗——旺旺旺,你一聽就知道這是什麼動物,對於不同的動物會有不同的結果,可以理解為多態。

Java多態也可以用一句話表示:父類的引用指向子類的對象。

多態存在的三個必要條件

1、繼承:在多態中必須存在有繼承關係的子類和父類。

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

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

2、多態的實現

 開始講了這麼多Java多態的基本概念,那麼到底用Java代碼怎麼體現出來呢?下麵我們直接看代碼。

public class Animal {

    public void eat(){
        System.out.println("動物吃東西");
    }

    public void skill(){
        System.out.println("動物有本領");
    }
}

class Dog extends Animal{

    public void eat() {
        System.out.println("狗吃骨頭");
    }

    public void skill(){
        System.out.println("狗會看家");
    }
}

class Cat extends Animal{
    public void eat() {
        System.out.println("貓吃魚");
    }

    public void skill(){
        System.out.println("貓會抓老鼠");
    }
}

//測試類
public class AnimalTest {
    
    public static void main(String[] args) {
        Animal dog=new Dog();
        dog.eat();
        dog.skill();
        Animal cat=new Cat();
        cat.eat();
        cat.skill();
    }
}

運行結果:

從上面的測試類來看,我們都是創建不同子類的對象,相同的父類引用,卻表現出它們不同的特征,這就是體現了Java的多態性。

如果你認為這樣還是不能體現多態的好處,我們在AnimalTest類中添加一個show()方法,來體會Java多態的好處。

如果Java中沒有多態的特性會是什麼樣的,下麵我們來看一下。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }

    public void show(Dog dog){
        dog.eat();
        dog.skill();
    }

    public void show(Cat cat){
        cat.eat();
        cat.skill();
    }
}

可以發現在show()方法中的形參都傳入了對象,而且重載3個相同的show()方法,如果我們有十個百個千個類需要傳入方法,那麼豈不是要重載上千個方法,可見這樣代碼的冗餘非常的大,非常不利於代碼的維護。

而如果有多態的話,只需寫一個show()方法即可。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }
}

 從這裡可以看出來多態的優點:

1、減少重覆代碼,使代碼變得簡潔(由繼承保證)。

2、提高了代碼的擴展性(由多態保證)。

但是也有缺點:子類單獨定義的方法會丟失。後面的向上轉型會介紹到。

多態其實是一種虛擬方法調用。在編譯期間,只能調用父類中聲明的方法,但是在運行期間,實際執行的是子類重寫父類的方法。

總結為一句話:編譯看左邊,運行看右邊(可能看到這句話會有點頭暈,但是理解下麵向上轉型的概念就應該能夠理解這句話了)。

3、向上轉型

子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是將子類對象轉為父類對象。此處父類對象也可以是介面。

我們用前面多態的例子舉例,只是在子類中添加了它們自己的方法,父類中沒有定義,如下。

public class Animal {

    public void eat(){
        System.out.println("動物吃東西");
    }

    public void skill(){
        System.out.println("動物有本領");
    }
}

class Dog extends Animal{

    public void eat() {
        System.out.println("狗吃骨頭");
    }

    public void skill(){
        System.out.println("狗會看家");
    }
    //新添加的方法
    public void run(){
        System.out.println("狗跑得快");
    }
}

class Cat extends Animal{
    public void eat() {
        System.out.println("貓吃魚");
    }

    public void skill(){
        System.out.println("貓會抓老鼠");
    }
    //新添加的方法
    public void life(){
        System.out.println("貓有九條命");
    }
}

//測試類
public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//向上轉型成Animal
        dog.eat();
        dog.skill();
        //dog.run();//Cannot resolve method 'run()'
        Animal cat = new Cat();//向上轉型成Animal
        cat.eat();
        cat.skill();
        //cat.life();//Cannot resolve method 'life()'
    }
}

這裡就產生了向上轉型,Animal dog= new Dog();Animal cat= new Cat();將子類對象Dog和Cat轉化為父類對象Animal。這個時候Animal這個引用調用的都是子類方法。再去調用子類單獨的方法就會報錯。如果非要調用也不是說不可以,那就要強轉了。既然現在已經是父類了,那就強轉為子類唄。

((Dog) dog).run();

((Cat) cat).life();

這樣也可以調用。但是千萬要註意,不能這樣轉,子類引用不能指向父類對象,Dog dog=(Dog)newAnimal();Cat cat = (Cat)new Animal();這樣是絕對不行的。就好像兒子可以生出爸爸一樣,這樣不和常理。

如果在向上轉型時,子類並沒有重寫父類的方法,那麼調用的就是父類中的方法。

到此為止,也可以證明前面說的一個結論:向上轉型會使子類單獨定義的方法會丟失。

4、向下轉型

與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉為子類對象。這裡我們就會想到,即然子類向上轉型為了父類,而子類又繼承了父類的屬性和方法,為什麼還要將父類轉型為子類。是因為對象的多態性只適用於方法,而不適用於屬性。所以當我們在使用多態的時候,就不能調用子類中的屬性和特有的方法了,所以需要向下轉型。(記憶體中實際上是載入了子類所特有的屬性和方法,但是由於變數聲明的是父類類型,導致在編譯時只能調用父類中聲明的屬性和方法,子類特有的屬性和方法不能調用)

我們還是使用向上轉型那裡的代碼為例(Anima、Dog、Cat類):

public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//Dog向上轉型成Animal
        Dog dog1= (Dog) dog;//向下轉型為Dog
        dog1.eat();
        dog1.skill();

        Cat cat=(Cat) dog;//java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat
        cat.eat();
        cat.skill();
    }
}

運行結果:

我們可以發現向下轉型為Dog沒有報錯,但是轉型為Cat卻報錯了,這個倒不難理解,因為開始向上轉型本來是Dog,然後再變回Dog,總不能Dog變成Cat吧。所以會報類型轉換錯誤。

向下轉型註意事項

  1. 向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)
  2. 向下轉型只能轉型為本類對象(貓是不能變成狗的)。

向下轉型我們一般會使用 instanceof 關鍵字來判斷:

使用方法:a instanceof A:判斷對象a是否為對象A的實例,如果是,返回true,如果不是,則返回false。

public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test=new AnimalTest();
        test.show(new Animal());
        test.show(new Dog());
        test.show(new Cat());
    }

    public void show(Animal animal){
        if (animal instanceof Dog){
            Dog dog= (Dog) animal;
            dog.eat();
            dog.skill();
            dog.run();
        }
    }
}

運行結果:

我們可以發現測試方法調用三次show方法,分別傳入了Animal、Dog、Cat對象,由於show()方法只判斷了Dog是否是該對象,所以Dog返回了true,輸出了Dog的信息,而其他的返回了false,則沒有輸出然後信息。

5、經典案例

 Java的多態和轉型都瞭解以後,現在趁熱打鐵,來點網上多態非常經典的例題:

來源:https://blog.csdn.net/Jian_Yun_Rui/article/details/52937791

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

運行的結果:

前面3個強行發現還能得到答案,但是從第4個之後就有點頭暈。

我們來慢慢分析第4個:首先是子類B類向上轉型為父類A,而子類B有重寫了父類A中的show(A obj)方法,所以a2變數能調用的只有父類A類中的show(D obj)和子類B中的show(A obj),而B是繼承自A類的,D繼承自B類,所以不可能調用show(D obj)方法,所以結果是4--B and A;剩下的依次類推。

當父類對象變數引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變數類型決定可調用的方法。如果子類中沒有覆蓋該方法,那麼會去父類中尋找。但是它仍然要根據繼承鏈中方法調用的優先順序來確認方法,該優先順序為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

6、總結

通過上面對多態的學習,可以小結一下概念:

1、Java多態也可以用一句話表示:父類的引用指向子類的對象。

2、運行時多態的前提:繼承,重寫,向上轉型。

3、多態能夠減少重覆代碼,使代碼變得簡潔;提高了代碼的擴展性。

4、多態其實是一種虛擬方法調用。歸結為一句話:編譯看左邊,運行看右邊。

5、向上轉型就是是將子類對象轉為父類對象

6、繼承鏈中對象方法的調用的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 最近工作中備受打擊,之前設計的很多程式都被老大否決,需要重構,讓我好好看看設計模式。之前對這一塊內容的確不怎麼重視,感覺枯燥無聊又派不上用場。後來沉下心來研究了一番... 我靠,原來如此,之前寫代碼的時候怎麼這麼傻逼,很多問題其實在一開始設計的時候就能避免。之前寫的都是些什麼鬼。 我們踩過的坑 ...
  • 架構雜談《五》 保證最終一致性的模式 在大規模、高併發服務化系統中,一個功能被拆分成多個具有功能單一的子功能,一個流程會有多個系統的多個單一功能的服務組合實現,如果使用兩階段提交協議和三階段提交協議,確實能解決系統間的一致性問題。其實現也比較複雜、成本比較高,最重要的是性能不夠好,相比來看,TCC協 ...
  • 前言 模式介紹 外觀模式相比較之下比較簡單,模式設計中定義是為子系統中的一組介面提供一個一致的界面,此模式定義了一個高層介面,這個介面是的這一子系統更加容易使用。 如果不理解呢,簡單些說就是外觀模式提供了為內部提供了同意的介面層,解耦了子系統和客戶端,這樣客戶端只需要知道外觀類存在即可,不需要知道具 ...
  • 第四章 分詞 下雨天留客天留我不留 本打算先介紹“簡單搜索”,對ES的搜索有一個直觀的感受。但在寫的過程中發現分詞無論如何都繞不過去。 查詢, 查詢都與分詞息息相關,索性先介紹分詞。 ES作為一個開源的搜索引擎,其核心自然在於搜索,而搜索不同於我們在MySQL中的 查詢語句,無論我們在百度搜索一個關 ...
  • [TOC] 一、分散式事務前奏 事務:事務是由一組操作構成的可靠的獨立的工作單元,事務具備ACID的特性,即原子性、一致性、隔離性和持久性。 本地事務:當事務由資源管理器本地管理時被稱作本地事務。本地事務的優點就是支持嚴格的ACID特性,高效,可靠,狀態可以只在資源管理器中維護,而且應用編程模型簡單 ...
  • 開篇:自己自學java拖拖拉拉的也有半年了,一學期里有很多事讓我停止學習java,轉眼間開學就要大四找工作了,想用一個暑假好好學習一些java,想掌握一門技術讓自己餓不死。本想每天寫一些筆記總結,發現那樣的話太費時間了,即便寫了,我自己都不看。最後,我決定還是以題的形式寫成文章,方便自己複習,同時也 ...
  • Sentinel API Github : "WIKI " Sphu (指明要保護的資源名稱) Tracer (指明調用來源,異常統計介面) ContextUtil(標示進入調用鏈入口) 流控規則(針對來源屬性) 降級規則 Sentinel Annotation 源碼: & 使用該註解重構上述方法 ...
  • 因為盤搜搜索出來的鏈接有很多已經失效了,影響找數據的效率,因此想到了用爬蟲來過濾出有效的鏈接,順便練練手~ 這是本次爬取的目標網址http://www.pansou.com,首先先搜索個python,之後打開開發者工具, 可以發現這個鏈接下的json數據就是我們要爬取的數據了,把多餘的參數去掉, 剩 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...