查看上節內容,請點擊上方連結關注公眾號,查看文章。
繼承
上節我們談到,將現實中的概念映射為程序中的概念,我們談了類以及類之間的組合,現實中的概念間還有一種非常重要的關係,就是分類,分類有個根,然後向下不斷細化,形成一個層次分類體系。這種例子是非常多的:
在自然世界中,生物有動物和植物,動物有不同的科目,食肉動物、食草動物、雜食動物等,食肉動物有狼、狗、虎等,這些又分為不同的品種 ...
打開電商網站,在顯著位置一般都有分類列表,比如家用電器、服裝,服裝有女裝、男裝,男裝有襯衫、牛仔褲等 ...
電腦程式經常使用類之間的繼承關係來表示對象之間的分類關係。在繼承關係中,有父類和子類,比如動物類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",或長按下圖二維碼關注)
原創文章,保留所有版權,轉載請聯繫後臺。