主頁 > 軟體設計 > Java學習 -- 多型性

Java學習 -- 多型性

2021-09-22 10:37:03 軟體設計

多型概述

多型性是繼封裝性和繼承性之后,面向物件的第三大特性,

多型性是面向物件編程的又一個重要特征,它是指在父類中定義的屬性和方法被子類繼承之后,可以具有不同的資料型別或表現出不同的行為,這使得同一個屬性或方法在父類及其各個子類中具有不同的含義,

對面向物件來說,多型分為編譯時多型運行時多型,其中編譯時多型是靜態的,主要是指方法的多載,它是根據引數串列的不同來區分不同的方法,通過編譯之后會變成兩個不同的方法,在運行時談不上多型,而運行時多型是動態的,它是通過動態系結來實作的,也就是大家通常所說的多型性,

多型的體現

在Java中物件的多型性體現在父類參考指向子類的物件

首先定義3個類,Person、Man、Woman,其中Person是Man和Woman的父類,

Person類

public class Person {
    String name;
    int age;

    public void eat(){
        System.out.println("吃飯");
    }

    public void walk(){
        System.out.println("走路");
    }

}

Man類

public class Man extends Person {
    boolean isDrinking;
    boolean isSmoking;

    public void earnMoney(){
        System.out.println("男人掙錢的方法");
    }

    @Override
    public void eat() {
        System.out.println("為了有力氣干活,我要吃2斤大肘子");
    }

    @Override
    public void walk() {
        System.out.print("男人邁著霸氣的步伐");
    }
}

Woman類

public class Woman extends Person{
    boolean isBeauty;

    public void goShopping() {
        System.out.println("女人購物的方法");
    }

    @Override
    public void eat() {
        System.out.println("為了大漂亮,我只能吃二兩水煮大白菜");
    }

    @Override
    public void walk() {
        System.out.print("女人邁著婀娜的步伐");
    }
}

多型之前實體化物件

參考變數是什么型別就new什么型別的物件

public void test1() {
	// 參考變數型別為Person,new的也是Person 
    Person p1 = new Person();
    Man man = new Man();
    Woman man = new Woman();
}

使用多型實體化物件

父類參考指向子類物件(或子類物件賦值給父類參考)

// 格式: 
父型別別 變數 = new 子類物件;
 	public void test2() {
        // 參考為父類,new的物件為子類
        Person p1 = new Man();
        Person p2 = new Woman();
    }

Java的參考變數有兩個型別,等號左邊型別稱為編譯時型別,等號右邊型別稱為運行時型別

編譯時型別:宣告參考變數的型別,

運行時型別:實際賦給參考變數的型別,

當編譯時型別和運行時型別不一致時,就產生了物件的多型性

向上轉型

如果是第一次接觸多型,可以會非常迷惑,為什么會存在這種定義的方式?

多型的定義方式,其實就是一種向上轉型的程序,對于如下的代碼大家肯定很容易理解:

short a = 10;
int b = a;  // 自動型別提升

我們知道基本資料型別間可以發生自動型別轉換,比如將一個short型別的變數賦值給int型別,范圍小的型別會自動提升為范圍大的型別,如果對應到類的繼承關系中,顯然父類的適用范圍更加廣泛,而子類的適用范圍更加具體,因此我們可以將子類的向上轉型看作是基本型別的自動型別轉換,

至于為什么叫做向上轉型而不叫向下轉型,在繼承關系圖中一般是將父類作為上一個層級的存在,而子類作為下一個層級的存在,當子類需要型別轉換為父類時,一般都形象的稱為向上轉型,而向下轉型則表示子類的參考指向父類的物件,這個概念將在本文后續介紹,

在這里插入圖片描述

大家肯定會有疑問,為什么子類的適用范圍要小于父類?明明子類繼承了父類所有的屬性和方法,并且還可以擁有自己特有的方法,這不是意味著子類用于的功能比父類更多嘛?

讓我們回到對于類的概念上,類是作為對一類具有相關屬性和行為的事物的描述,其中的屬性和行為越多對于事物的描述也就越具體,所以對比子類和父類,顯然對于子類的描述更加的具體,而對于父類的描述更加的抽象,父類所涵蓋的范圍也就更加廣泛,

向上轉型的三種時機

  1. 直接賦值
 	public void test2() {
        // 向上轉型,子類物件賦值給父類參考
        Person p1 = new Man();
        Person p2 = new Woman();
        Person p3;
        Man m1 = new Man();
        p3 = m1;  // 父類參考指向了子類參考所指向的子類物件(表達有點繞,其實是一樣的)
    }
  1. 方法的傳參
public void polymorphic(Person p1){
    //...
} 	
public void test2() {
        // 傳參中將子類物件作為實參傳遞給方法的父類參考形參,
        polymorphic(new Man());
}
  1. 方法的回傳值
// 方法的回傳值為Person,將Person的子類物件作為回傳值傳遞
public Person polymorphic1(){
    Man m = new Man();
    return m;
} 
public void test3(){
    // 接收方法的回傳值也應該使用Person型別的參考變數接收
    Person p1 = polymorphic1();
}

子類向上轉型后發生的改變

當子類向上轉型為父類后,其參考型別就是父型別別,通過父類的參考是無法訪問到子類物件中特有的屬性和方法,只能訪問父類中存在的屬性和方法,

// 試圖通過父類的參考訪問子類特有的屬性和方法時,編譯會報錯
public void test2() {
        Person p1 = new Man();
        Person p2 = new Woman();

        p1.isDrinking = false;
        p1.isSmoking = false;
        p1.earnMoney();

        p2.isBeauty = false;
        p2.goShopping();
}

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

多型的使用

呼叫方法

如果大家可以接受向上轉型的機制,我們接下來繼續看看當通過多型進行呼叫方法時,會發生什么情況,

    public void test3(){
        Person p1 = new Man();
        Person p2 = new Woman();
		
       // 通過多型的方式呼叫父類存在的方法
        p1.eat();
        p1.walk();
        p2.eat();
        p2.walk();
    }

在這里插入圖片描述
從輸出結果這里看到,使用多型的方式呼叫父類中存在的方法時,實際上呼叫的是子類覆寫重寫后的方法,這里需要引入方法系結的概念,

系結是指將一個方法呼叫同一個方法主題關聯起來,Java中的系結分為靜態系結(前期系結)和動態系結(后期系結),

靜態系結,程式執行前方法已經被系結,此時由編譯器或其它連接程式實作,在Java語言中,privatestaticfinal所修飾的方法以及構造器在編譯期間已經被系結,編譯器準確的知道應該呼叫哪個方法,這種呼叫方式被稱作靜態系結,

動態系結,程式執行前編譯器不知道物件的實際型別,在程式運行期間,JVM通過父類參考獲取其參考物件的實際型別,并定位到方法區中的該類的方法表,找到對應的方法進行呼叫,在Java中除了上述提到的幾種靜態系結方法以外,其它所有的方法都屬于動態系結,而動態系結是實作多型的關鍵

Java在呼叫方法時,編譯期間會首先確定父類參考中是否存在所需要對應的方法,如果沒有則會向其父類中查找,直到找到Object類,如果Object類中仍然沒有該方法則會編譯報錯,

如果存在該方法時,編譯期間所呼叫的就是父類中存在的方法,在多型的前提下(即子類重寫了父類的方法時)父類被呼叫的方法也被稱作虛擬方法

在這里插入圖片描述

在運行期間,就發生了上面所介紹的動態系結,JVM會根據所呼叫的方法前的參考,找到實際的運行時型別并呼叫其中的所對應方法,如果沒有則向上查找,在本例中如果Man和Woman類中沒有覆寫重寫父類的方法,那么實際呼叫的還是父類的方法,不過這種情況完全沒有必要再使用多型的方式創建物件,

總結多型在呼叫方法時的特點就是:編譯看左運行看右

呼叫屬性

多型性并不適用于屬性,因為在繼承性中屬性是不會被重寫的,這也意味著子類中的重名屬性是屬于子類特有的屬性與父類無關,以多型的方式呼叫屬性時遵循編譯看左,運行也看左的規則,

  1. 編譯看左:在編譯期間會確定父類中是否存在對應屬性,沒有則編譯錯誤,
  2. 運行看左:運行期間父類參考所呼叫屬性也是父類中的屬性,即使子類中存在重名屬性也不會呼叫該屬性,

例如在子類中定義一個與父類同名的屬性:

當使用多型的方式呼叫name屬性時,實際上呼叫的時父類的name
在這里插入圖片描述

多型存在的必要條件

  • 繼承

    多型必須是在繼承的基礎之上才能產生的,如果兩個類不存在繼承關系,則更談不上下面所需的重寫和向上轉型,

  • 重寫

    如果子類沒有覆寫重寫父類的方法,那么多型的所要體現出的動態呼叫不同方法的意義也就不存在,因此也就沒有必要使用多型創建物件,

  • 向上轉型

    向上轉型也就是父類參考指向子類的物件,通過這種方式,可以使得一個父類(介面)接受不同的子類物件完成各自不同的功能,

多型的優點

使用多型可以提高我們開發的便利性和代碼的拓展性,如何理解這段話呢?

例如,需要定義一個功能,該功能實作了對餐廳對人類物件的服務,餐廳并不關心其所服務的物件是男人還是女人,只要是人該餐廳都能為其提供服務,

為了完成上述的功能,如果在沒有多型的前提下,我們需要分別為男人和女人同時定義一個功能相同的方法,

    // 為男人定義該功能
    public void Service(Man m){
        m.walk();
        System.out.println("走進餐廳");
        System.out.println("請問您需要些什么?");
        m.eat();
        System.out.println("好的請您稍等");
    }
    
	// 為男人定義該功能
    public void Service(Woman f){
        f.walk();
        System.out.println("走進餐廳");
        System.out.println("請問您需要些什么?");
        f.eat();
        System.out.println("好的請您稍等");
    } 

如果使用多型來完成該項功能,我們可以只需要將接收物件的類定義為兩個類的父類就可以在一個方法中完成對不同物件的服務,

    public void Service(Person p){
        p.walk();
        System.out.println("走進餐廳");
        System.out.println("請問您需要些什么?");
        p.eat();
        System.out.println("好的請您稍等");
    }

在這里插入圖片描述
這里例子只存在兩個子類,如果是需要服務非常多的類,并且這些類都具有公共父類時,多型的好處就體現的非常明顯,可以使得我們極大的減少冗余的代碼,

同時在擴展性方法,不管今后添加多少子類,我們都可以使用這同一個方法來完成,

向下轉型

在前文中已經介紹了向上轉型的概念,而向下轉型的概念就是子類的參考指向父類的物件

首先需要明確的是向下轉型屬于強制型別轉換,我們類比到基本資料型別的強制型別轉換,基本資料型別在強制型別轉換時可能會發生資料截斷,而作為對于類來說這個程序同樣是不安全的,因此在實際開發中需要謹慎使用,

向下轉型的格式

向下轉型同樣使用強制型別轉換符()來進行轉型,定義格式如下:

子型別別 變數名 = (子型別別) 父類變數名;  

例如:使用Man的參考接收Person的物件,當程式運行時會發生什么?

Man m = (Man) new Person();

在這里插入圖片描述
程式拋出了型別轉換例外ClassCastException,我們分別從概念和代碼層面上來討論為什么程式會報錯,

從概念上來說

一個男人可以是一個人(is a),但我們不能說一個人是一個男人,顯然人的表示范圍更加廣泛,因此從道德倫理上來說,這種行為也是反常態的,

從代碼上來說

子類中的屬性和方法一定是等于或者多于父類中的屬性和方法,那么我們如果我們將一個父類的物件成功賦值給子類的參考時,通過這個子類的參考去呼叫子類中特有的方法,父類的物件是不存在這些方法的,這顯然就會發生更加嚴重的錯誤,所以這種行為是不被允許的,

那如果我們將一個向上轉型的Woman類強轉為Man類時又會發生什么呢?

Person p = (Man) new Woman();
Man m = (Man) new Person();

在這里插入圖片描述

程式也拋出了型別轉換例外,同樣從概念和代碼上進行討論

從概念上來說

讓一個女人變成一個男人顯然不是Java力所能及的范圍,

從代碼上來說

兩個繼承于同一個父類的不同子類他們的內部結構肯定是不相同的,如果允許這種行為的發生,同樣會導致無法預測的錯誤,

instanceof

上面的舉例說明了向下轉型中存在的隱患,那么向下轉型就沒有用了嘛?顯然也不能一概否定,當我們在對子類物件進行向上轉型后又需要向下轉型來呼叫子類特有的屬性和方法時,向下轉型的作用就體現出來了,但是鑒于上面可能發生的例外,我們應該在向下轉型之前使用instanceof運算子來判斷該物件是否為需要轉型后的物件,

使用格式

參考變數 instanceof 類A  // 檢查該參考所指向的物件是否為類A的實體,如果是回傳true,否則回傳false

例如:定義一個方法,該方法使用了父類的參考接收物件,在其中需要呼叫子類所特有的屬性和方法,

    public void instanceOfTest(Person p) {
        if (p instanceof Man) {
            Man m = (Man) p;
            m.earnMoney();
            m.isDrinking = false;
        } else if (p instanceof Woman) {
            Woman f = (Woman) p;
            f.goShopping();
            f.isBeauty = true;
        }
    }

在這里插入圖片描述
對于向下轉型和instanceof操作我們應該盡量減少使用,因為這種轉型是無法被編譯器察覺出錯誤的,只有在程式運行期間才會拋出例外,當我們需要使用子類特有的方法時,應該首先檢查父類的設計是否合理,而不是直接使用向下轉型,

總結

關于多型性,屬于面向物件三大特性中最為抽象的一種特性,對于它的理解也更加困難,具體還需要落實到代碼層面多多體會,理解好了多型性對于抽象類和介面的理解也會更加深刻,

最后參考《Java編程思想》作者 Bruce Eckel 的一句話:不要犯傻,如果它不是晚系結(動態系結), 它就不是多型

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/301999.html

標籤:其他

上一篇:?? 中秋佳節相約C語言進階 ?? 字串函式與記憶體函式 【建議收藏】

下一篇:你真的會給變數命名嗎

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more