電腦程式的思維邏輯 (15) - 初識繼承和多態

2021-02-25 老馬說編程

查看上節內容,請點擊上方連結關注公眾號,查看文章。

繼承

上節我們談到,將現實中的概念映射為程序中的概念,我們談了類以及類之間的組合,現實中的概念間還有一種非常重要的關係,就是分類,分類有個根,然後向下不斷細化,形成一個層次分類體系。這種例子是非常多的:

在自然世界中,生物有動物和植物,動物有不同的科目,食肉動物、食草動物、雜食動物等,食肉動物有狼、狗、虎等,這些又分為不同的品種 ...

打開電商網站,在顯著位置一般都有分類列表,比如家用電器、服裝,服裝有女裝、男裝,男裝有襯衫、牛仔褲等 ...

電腦程式經常使用類之間的繼承關係來表示對象之間的分類關係。在繼承關係中,有父類和子類,比如動物類Animal和狗類Dog,Animal是父類,Dog是子類。父類也叫基類,子類也叫派生類,父類子類是相對的,一個類B可能是類A的子類,是類C的父類。

之所以叫繼承是因為,子類繼承了父類的屬性和行為,父類有的屬性和行為,子類都有。但子類可以增加子類特有的屬性和行為,某些父類有的行為,子類的實現方式可能與父類也不完全一樣。

使用繼承一方面可以復用代碼,公共的屬性和行為可以放到父類中,而子類只需要關注子類特有的就可以了,另一方面,不同子類的對象可以更為方便的被統一處理。

本節主要通過圖形處理中的一些簡單例子來介紹Java中的繼承,會介紹繼承的基本概念,關於繼承更深入的討論和實現原理,我們在後續章節介紹。

Object

在Java中,所有類都有一個父類,即使沒有聲明父類,也有一個隱含的父類,這個父類叫Object。Object沒有定義屬性,但定義了一些方法,如下圖所示:


本節我們會介紹toString()方法,其他方法我們會在後續章節中逐步介紹。toString()方法的目的是返回一個對象的文本描述,這個方法可以直接被所有類使用。

比如說,對於我們之前介紹的Point類,可以這樣使用toString方法:

Point p = new Point(2,3);

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

輸出類似這樣:

Point@76f9aa66

這是什麼意思呢?@之前是類名,@之後的內容是什麼呢?我們來看下toString的代碼:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

getClass().getName()返回當前對象的類名,hashCode()返回一個對象的哈希值,哈希我們會在後續章節中介紹,這裡可以理解為是一個整數,這個整數默認情況下,通常是對象的內存地址值,Integer.toHexString(hashCode())返回這個哈希值的16進位表示。

為什麼要這麼寫呢?寫類名是可以理解的,表示對象的類型,而寫哈希值則是不得已的,因為Object類並不知道具體對象的屬性,不知道怎麼用文本描述,但又需要區分不同對象,只能是寫一個哈希值。

但子類是知道自己的屬性的,子類可以重寫父類的方法,以反映自己的不同實現。所謂重寫,就是定義和父類一樣的方法,並重新實現。

Point類 - 重寫toString()

我們再來看下Point類,這次我們重寫了toString()方法。


toString方法前面有一個 @Override,這表示toString這個方法是重寫的父類的方法,重寫後的方法返回Point的x和y坐標的值。重寫後,將調用子類的實現。比如,如下代碼的輸出就變成了:(2,3)

Point p = new Point(2,3);

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

圖形處理類

接下來,我們以一些圖形處理中的例子來進一步解釋,先來看幅圖:


這都是一些基本的圖形,圖形有線、正方形、三角形、圓形等,圖形有不同的顏色。接下來,我們定義以下類來說明關於繼承的一些概念:

父類Shape,表示圖形。

類Circle,表示圓。

類Line,表示直線。

類ArrowLine,表示帶箭頭的直線,

圖形 (Shape)

所有圖形都有一個表示顏色的屬性,有一個表示繪製的方法,下面是代碼:


以上代碼基本沒什麼可解釋的,實例變量color表示顏色,draw方法表示繪製,我們不會寫實際的繪製代碼,主要是演示繼承關係。

圓 (Circle)

圓繼承自Shape,但包括了額外的屬性,中心點和半徑,以及額外的方法area,用於計算面積,另外,重寫了draw方法,代碼如下:


說明幾點:

Java使用extends關鍵字標明繼承關係,一個類最多只能有一個父類。

子類不能直接訪問父類的私有屬性和方法,比如,在Circle中,不能直接訪問shape的私有實例變量color。

除了私有的外,子類繼承了父類的其他屬性和方法,比如,在Circle的draw方法中,可以直接調用getColor()方法。

看下使用它的代碼:


程序的輸出為:

draw circle at (2,3) with r 2.0, using color : black
12.566370614359172

這裡比較奇怪的是,color是什麼時候賦值的?在new的過程中,父類的構造方法也會執行,且會優先於子類先執行。在這個例子中,父類Shape的默認構造方法會在子類Circle的構造方法之前執行。關於new過程的細節,我們會在後續章節進一步介紹。

直線 (Line)

線繼承自Shape,但有兩個點,有一個獲取長度的方法,另外,重寫了draw方法,代碼如下:


這裡我們要說明的是super這個關鍵字,super用於指代父類,可用於調用父類構造方法,訪問父類方法和變量:

可以看出,super的使用與this有點像,但super和this是不同的,this引用一個對象,是實實在在存在的,可以作為函數參數,可以作為返回值,但super只是一個關鍵字,不能作為參數和返回值,它只是用於告訴編譯器訪問父類的相關變量和方法。

帶箭頭直線 (ArrowLine)

帶箭頭直線繼承自Line,但多了兩個屬性,分別表示兩端是否有箭頭,也重寫了draw方法,代碼如下:


ArrowLine繼承自Line,而Line繼承自Shape,ArrowLine的對象也有Shape的屬性和方法。

注意draw方法的第一行,super.draw()表示調用父類的draw()方法,這時候不帶super.是不行的,因為當前的方法也叫draw()。

需要說明的是,這裡ArrowLine繼承了Line,也可以直接在類Line裡加上屬性,而不需要單獨設計一個類ArrowLine,這裡主要是演示繼承的層次性。

圖形管理器

使用繼承的一個好處是可以統一處理不同子類型的對象,比如說,我們來看一個圖形管理者類,它負責管理畫板上的所有圖形對象並負責繪製,在繪製代碼中,只需要將每個對象當做Shape並調用draw方法就可以了,系統會自動執行子類的draw方法。代碼如下:


ShapeManager使用一個數組保存所有的shape,在draw方法中調用每個shape的draw方法。ShapeManager並不知道每個shape具體的類型,也不關心,但可以調用到子類的draw方法。

我們來看下使用ShapeManager的一個例子:
新建了三個shape,分別是一個圓、直線和帶箭頭的線,然後加到了shape manager中,然後調用manager的draw方法。

需要說明的是,在addShape方法中,參數Shape shape,聲明的類型是Shape,而實際的類型則分別是Circle,Line和ArrowLine。子類對象賦值給父類引用變量,這叫向上轉型,轉型就是轉換類型,向上轉型就是轉換為父類類型。

變量shape可以引用任何Shape子類類型的對象,這叫多態,即一種類型的變量,可引用多種實際類型對象。這樣,對於變量shape,它就有兩個類型,類型Shape,我們稱之為shape的靜態類型,類型Circle/Line/ArrowLine,我們稱之為shape的動態類型。在ShapeManager的draw方法中,shapes[i].draw()調用的是其對應動態類型的draw方法,這稱之為方法的動態綁定。

為什麼要有多態和動態綁定呢?創建對象的代碼 (ShapeManager以外的代碼)和操作對象的代碼(ShapeManager本身的代碼),經常不在一起,操作對象的代碼往往只知道對象是某種父類型,也往往只需要知道它是某種父類型就可以了。

可以說,多態和動態綁定是電腦程式的一種重要思維方式,使得操作對象的程序不需要關注對象的實際類型,從而可以統一處理不同對象,但又能實現每個對象的特有行為。後續章節我們會進一步介紹動態綁定的實現原理。

小結

本節介紹了繼承和多態的基本概念:

每個類有且只有一個父類,沒有聲明父類的其父類為Object,子類繼承了父類非private的屬性和方法,可以增加自己的屬性和方法,可以重寫父類的方法實現。

new過程中,父類先進行初始化,可通過super調用父類相應的構造方法,沒有使用super的話,調用父類的默認構造方法。

子類變量和方法與父類重名的情況下,可通過super強制訪問父類的變量和方法。

子類對象可以賦值給父類引用變量,這叫多態,實際執行調用的是子類實現,這叫動態綁定。

但關於繼承,還有很多細節,比如實例變量重名的情況。另外,繼承雖然可以復用代碼,便於統一處理不同子類的對象,但繼承其實是把雙刃劍,使用不當,也有很多問題。讓我們下節來討論這些問題,而關於繼承和多態的實現原理,讓我們再下節來討論。

-- 長文連載,未完待續,敬請關注(點擊文章頭部公眾號連結,或公眾號搜索"老馬說編程"或"laoma_shuo",或長按下圖二維碼關注)

原創文章,保留所有版權,轉載請聯繫後臺。

相關焦點

  • C中的繼承和多態
    有了繼承和多態,我們可以完成代碼重用。在C中有許多技巧可以實現多態。本文的目的就是演示一種簡單和容易的技術,在C中應用繼承和多態。通過創建一個VTable(virtual table)和在基類和派生類對象之間提供正確的訪問,我們能在C中實現繼承和多態。VTable能通過維護一張函數表指針表來實現。為了提供基類和派生類對象之間的訪問,我們可以在基類中維護派生類的引用和在派生類中維護基類的引用。
  • python中類的繼承和多態
    繼承是為了代碼復用和設計復用而設計的,是面向對象程序設計的重要特徵之一。當我們設計一個新類時,如果可以繼承一個已有的設計良好的類然後進行二次開發,無疑會大幅度減少開發工作量。1.類的繼承類繼承語法:Class 派生類名(基類名): #基類名寫在括號裡派生類成員在繼承關係中,已有的,設計好的類稱為父類或基類,新設計的類稱為子類或派生類。派生類可以繼承父類的公有成員,但是不能繼承其私有成員。
  • 繼承、接口與多態的相關問題
    繼承而得到的類稱為子類,被繼承的類稱為父類。子類不能繼承父類中訪問權限為private的成員變量和方法。子類可以重寫父類的方法,及命名與父類同名的成員變量。但Java不支持多重繼承,即一個類從多個超類派生的能力。
  • Java 中的繼承和多態(深入版)
    在這三個特性中,如果沒有封裝和繼承,也不會有多態。那麼多態實現的途徑和必要條件是什麼呢?以及多態中的重寫和重載在JVM中的表現是怎麼樣?在Java中是如何展現繼承的特性呢?對於子類繼承於父類時,又有什麼限制呢?本文系基礎,深入淺出過一遍 Java 中的多態和繼承。多態是同一個行為具有多個不同表現形式或形態的能力。
  • C語言實現繼承和多態
    背景 寫這篇文章的是因為前幾天看了一個火狐的郵箱代碼,純C寫的,很佩服他們能用C寫出多態、繼承的形式來
  • 淺析java繼承與多態,抽象類與接口的區別
    面向對象的設計可以更好的解決系統維護、擴展、和代碼重用的需求,代碼過多後過程式的邏輯組織會變得異常複雜,所以就發展出了面向對象式編程。而面向對象的基本特性就是封裝、繼承、與多態。一、繼承extends 關鍵字表示繼承某個已存在的類,已存在的類稱為「超類」、「基類」、或「父類」,而新繼承的類稱為「子類」、「派生類」。子類可以方法父類中的所有非 private 域,也即子類通過繼承獲得了父類具有的特性和能力。
  • 面向對象特性之三:多態(Python進階教程)
    今天我們要講一講面向對象的第三個特性:多態;什麼叫做多態?從字面意思上理解,就是一個函數具有多種形態。本質上其實就是同一操作方法作用於不同的對象時,有著不同的解釋,執行不同的邏輯,產生不同的結果。今天我們還是用例子來說明在Python中多態的使用方法、注意事項和使用的優點。
  • 黑馬程式設計師:Python封裝、繼承和多態以及類方法等習題
    使用public關鍵字 C.使用__XX__定義屬性名 D.使用__XX定義屬性名2.下列選項中,不屬於面向對象程序設計的三個特徵的是()。繼承 D.多態3.以下C類繼承A類和B類的格式中,正確的是()。
  • 第79p,什麼是多態,多態有什麼特點?
    大家好,我是楊數Tos,這是《從零基礎到大神》系列課程的第79篇文章,第三階段的課程:Python進階知識:類與對象(十);面向對象編程3大特性之多態。一、什麼是多態?多態指的是同一種事物有多種形態;比如:水和冰都是水,人既是人也是動物;多態帶來的特性是可以在不考慮對象具體類型的情況下使用對象;比如:只要是人就一定會說話、吃飯、呼吸,而不用考慮他是老人還是小孩子;多態性帶來的好處在於減少使用者的學習成本;比如:我們考駕駛的時候只需要考汽車駕照就可以了
  • 淺析 Python 的類、繼承和多態
    繼承舉一個教科書中最常見的例子。Circle 和 Rectangle 繼承自 Shape,不同的圖形,面積(area)計算方式不同。Python 以最少的新的語法和語義實現了類機制,這一點確實讓人驚嘆,但是也讓 C++ / Java 程式設計師感到頗為不適。多態如前所述,Python 沒有覆寫(override)的概念。嚴格來講,Python 並不支持「多態」。
  • AP | AP計算機A & AP計算機原理
    考試以JAVA語言為基礎,學習計算機的基本知識,了解JAVA語言的特點,學習計算機程式語言,學習面向對象程序設計的思想以及簡單的算法。它注重於培養學生的能力,包括學習能力,計算機操作能力,分析問題能力,代碼理解能力和程序設計的能力。
  • c++是如何實現多繼承帶來的多態問題?
    多態是指同樣的消息被不同類型的對象接收時導致完全不同的的行為。有虛函數的類才能叫多態類型的類,可以從探索虛函數是如何實現動態綁定的來了解如何實現多繼承中的多態。單繼承時虛函數動態綁定的實現原理每個類各有一個虛表(虛函數表),虛表的內容是由編譯器安排的。
  • Java中使用接口實現多繼承和多態的方法
    1.JAVA裡沒有多繼承,一個類之能有一個父類。  而繼承的表現就是多態。一個父類可以有多個子類,而在子類裡可以重寫父類的方法(例如方法print()),這樣每個子類裡重寫的代碼不一樣,自然表現形式就不一樣。
  • 計算機思維的邏輯基礎是什麼?
    l 計算機思維的邏輯基礎計算機思維是指人們操作計算機時,計算機行使特定功能的運作方式。邏輯基礎則是指支撐事物運作的基本法則。因而,計算機思維的邏輯基礎可以理解為,計算機在行使特定功能時,其運作方式背後的法則——即「分離」。不同於人腦的思維方式,計算機思維本身是不具有體現的。但是其行使法則之中與人腦最為不同的一點在於思想與方法、思想與對象、對象與方法的分離,這也是計算機能達到高效與高性能運算的邏輯基礎。
  • C語言中的面向對象(1)-類模擬和多態,繼承
    正在閱讀:C語言中的面向對象(1)-類模擬和多態,繼承C語言中的面向對象(1)-類模擬和多態,繼承2005-01-12 10:04出處:作者:liyuming1978所以說類包含了兩個範疇:數據和操作。而C語言中的struct僅僅是數據的集合。
  • 2016年4月成人自考計算機基礎與程序設計真題
    1.依據所使用的電子元件,將電子計算機的發展可分為4個階段,其中第2階段是( )A.電子管B.大規模超大規模集成電路C.電晶體D.中小規模集成電路2.下列不屬於結構化軟體開發方法的基本要點的是( )A.結構化編碼B.模塊化設計C.多態D.逐步求精
  • 【4】類的繼承和多態-簡易的Python面向對象教程
    面向對象(2) - 實例方法 (本文) 面向對象(3) - 類屬性和類方法 面向對象(4) - 繼承和多態,以及一個綜合小遊戲案例
  • 軟體特攻隊|針對C++編譯期多態與運行期多態,我有話說
    在C++的面向對象編程中,多態是OO三大特性之一,我們稱為運行期多態,也稱它為動態多態;但在泛型編程中,多態是基於模板的具現化與函數的重載解析,由於這種多態發生於編譯期,所以稱它為編譯期多態或靜態多態。運行期多態運行期多態的設計來源於類繼承體系的設計上。
  • Java多態性:Java什麼是多態?
    多態性是面向對象編程的又一個重要特徵,它是指在父類中定義的屬性和方法被子類繼承之後,可以具有不同的數據類型或表現出不同的行為,這使得同一個屬性或方法在父類及其各個子類中具有不同的含義
  • (65) 線程的基本概念 / 電腦程式的思維邏輯
    在之前的章節中,我們都是假設程序中只有一條執行流,程序從main方法的第一條語句逐條執行直到結束。從本節開始,我們討論並發,在程序中創建線程來啟動多條執行流,並發和線程是一個複雜的話題,本節,我們先來討論Java中線程的一些基本概念。創建線程線程表示一條單獨的執行流,它有自己的程序執行計數器,有自己的棧。