繼承是面向物件程式設計的基石之一。解構函式是一種在物件被銷燬時可以被自動呼叫的函式。“is -a”(是一個)的關係是用繼承來表達的,而“has-a”(有一個)的關係則是用組合來表達的。
final關鍵字,無法改變的,final類禁止繼承,final類中所有的方法都隱式指定為是final的,因此無法覆蓋它們。
儘管面向物件程式設計對繼承極力強調,但在開始一個設計時,一般應優先選擇組合(或者可能是代理),只在確實必要時才使用繼承。因為組合更具靈活性。此外,透過對成員型別使用繼承技術的新增技巧,可以在執行時改變那些成員物件的型別和行為。因為可以在執行時改變組合而成的物件的行為。
在面向物件的程式設計語言中,多型是繼資料抽象和繼承之後的第三種基本特徵。多型透過分離做什麼和怎麼做,從另一角度將介面和實現分離開來。多型不但能夠改善程式碼的組織結構和可讀性,還能夠建立可拓展的程式,即無論在專案最初建立時還是在需要新增新功能時都可以“生長”的程式。多型的作用則是消除型別之間的耦合關係。Java中除了static方法和final方法(private方法屬於final方法)之外,其他的所有方法都是後期繫結,使用final能夠透過編譯器有效的關閉動態繫結。構造器並不具有多型性(他們實際上是static方法,只不過該static宣告是隱式的)。基類的構造器總是在匯出類的構造過程中被呼叫,而且按照繼承層次逐漸向上連結,以使每個基類的構造器都能得到呼叫。複雜物件呼叫構造器要遵循下面的順序:
1)呼叫基類構造器。這個步驟會不斷地反覆遞迴下去,首先是構造這種層次結構的根,然後是下一層匯出類,等等,知道最底層的匯出類。
2)按宣告順序呼叫成員的初始化方法;
3)呼叫匯出類構造器的主體。
銷燬的順序與初始化順序相反
package com.exam.cn.test;class Glyph { void draw() { System.out.println("Glyph.draw()"); } public Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); }}class RoundGlyph extends Glyph{ private int radius=1; public RoundGlyph(int r) { radius=r; System.out.println("RoundGlyph.RoundGlyph(),radius="+radius); } void draw(){ System.out.println("RoundGlyph.draw(),radius="+radius); }}public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); }}輸出結果Glyph() before draw()RoundGlyph.draw(),radius=0Glyph() after draw()RoundGlyph.RoundGlyph(),radius=5
透過上面這個例子,我們會看到問題之所在:Glyph.draw()方法設計為將要被覆蓋,這種覆蓋是在RoundGlyph中發生的,但是Glyph構造器會呼叫這個方法,結果導致了對RoundGlyph.draw()被呼叫,這看起來似乎是我們的目的,但是如果看到輸出結果,我們會發現當Glyph的構造器呼叫draw方法時,radius不是預設初始化值1,而是0。初始化的實際過程是:
1)在其他任何事物發生之前,將分配給物件的儲存空間初始化成二進位制的零。
2)如前所述那樣呼叫基類構造器。此時,呼叫被覆蓋的draw()方法(要在呼叫RoundGlyph構造器之前呼叫),由於步驟1的緣故,我們次還是會發現radius的值為0.
3)按照宣告的順序呼叫成員的初始化方法。
4)呼叫匯出類的構造器主題。
諸如此類的錯誤會很容易被人忽略,而且要花很長的時間才能發現它。因此編寫構造器時有一條有效的準則:“用盡可能簡單地方法使物件進入正常狀態;如果可以的話,避免呼叫其他方法。”在構造器內唯一能夠安全呼叫的那些方法是基類中的final方法(也適用於private,它們自動屬於final方法)。這些方法不能被覆蓋,因此也就不會出現上述令人驚訝的問題。