繼承(inheritance)是面向對象的重要概念。繼承是除組合(composition)之外,提高代碼重復可用性(reusibility)的另一種重要方式。我們在組合(composition)中看到,組合是重復調用對象的功能接口。我們將看到,繼承可以重復利用已有的類的定義。
我們之前定義類的時候,都是從頭開始,詳細的定義該類的每一個成員。比如下面的Human類:
class Human { /** * accessor */ public int getHeight() { return this.height; } /** * mutator */ public void growHeight(int h) { this.height = this.height + h; } /** * breath */ public void breath() { System.out.println("hu...hu..."); } private int height; }
從上面的類定義,我們可以了解該類的所有細節: 該類的數據成員,該類的方法,該類的接口。
現在要定義一個新的類,比如Woman類,並假設Woman與Human類相當類似:
Human & Woman 我們可以像以前一樣,從頭開始,完整的定義Woman類:class Woman { /** * accessor */ public int getHeight() { return this.height; } /** * mutator */ public void growHeight(int h) { this.height = this.height + h; } /** * breath */ public void breath() { System.out.println("hu...hu..."); } /** * new method */ public Human giveBirth() { System.out.println("Give birth"); return (new Human(20)); } private int height; }
一個程序員在寫上面程序的時候,會有很大的煩惱。許多定義都曾在Human類中寫過,但我們還要重新敲一遍。Woman類只新增了一個giveBirth()方法 (該方法創建並返回一個新的Human對象)。
利用繼承,我們可以避免上面的重復。讓Woman類繼承自Human類,Woman類就自動擁有了Human類中所有public成員的功能。
我們用extends關鍵字表示繼承:
class Woman extends Human { /** * new method */ public Human giveBirth() { System.out.println("Give birth"); return (new Human(20)); } }這樣,我們就省去了大量的輸入。通過繼承,我們創建了一個新類,叫做衍生類(derived class)。被繼承的類(Human)稱為基類(base class)。衍生類以基類作為自己定義的基礎,並補充基類中沒有定義的giveBirth()方法。繼承關系可以表示為: 可以用以下Test類測試:
public class Test { public static void main(String[] args) { Woman aWoman = new Woman(); aWoman.growHeight(120); System.out.println(aWoman.getHeight()); } }
通過繼承,我們創建了Woman類。整個過程可以分為三個層次:基類定義,衍生類定義,外部使用。
基類定義的層次就是正常的定義一個類,比如上面的Human類定義。
在外部使用者看來(比如Test類中創建Woman類對象),衍生類有一個統一的外部接口:
對於外部使用者來說,上述接口就已經足夠了。僅從接口看,衍生類也沒有什麼特別之處。
然而,當程序員在衍生類定義的層次時,就必須要小心:
首先,接口是混合的: getHeight()方法和growHeight()方法來自基類,giveBirth()方法則是在衍生類內部定義的。
還有更加復雜的地方。我們之前在類的內部可以自由訪問類的成員(利用this指代對象)。然而,當我們在Woman類的定義范圍內,我們無法訪問基類Human的private成員。我們記得private的含義: private的成員僅供該類內部使用。Woman類是一個不同於Human類的新類,所以位於Human類的外部。在衍生類中,不能訪問基類的private成員。
但有趣的是,我們的growHeight()和getHeight()方法依然可以運行。這說明基類的private成員存在,我們只是不能直接訪問。
為了清晰概念,我們需要了解衍生類對象的生成機制。當我們創建一個衍生類的對象時,Java實際上先創建了一個基類對象(subobject),並在基類對象的外部(注意,這裡是基類對象的外部,衍生類對象的內部),增加衍生類定義的其他成員,構成一個衍生類對象。外部使用者能看到的,就是基類和衍生類的public成員。如下圖:
基類對象與衍生類對象 圖中黃色為基類對象。基層的成員之間可以互相訪問 (利用Human類定義中的this指代基類對象)。我們之前介紹了兩個訪問權限相關的關鍵字,private和public,它們控制了成員的外部可見性。現在,我們介紹一個新的訪問權限關鍵字:protected。
標為protected的成員在該類及其衍生類中可見。這個概念很容易理解,就是說,基類的protected成員可以被衍生層訪問,但不能被外部訪問,如下圖:
衍生類對象的外部接口最終由基類對象的public成員和衍生層的public成員共同構成。如果基類public成員和衍生層的public成員同名,Java接口中呈現的究竟是哪一個呢?
我們在構造方法與方法重載中已經提到,Java是同時通過方法名和參數列表來判斷所要調用的方法的。方法是由方法名和參數列表共同決定的。上述問題中,如果只是方法名相同,而參數列表不同,那麼兩個方法會同時呈現到接口,不會給我們造成困擾。外部調用時,Java會根據提供的參數,來決定使用哪個方法 (方法重載)。
如果方法名和參數列表都相同呢? 在衍生層時,我們還可以通過super和this來確定是哪一個方法。而在外部時,我們呈現的只是統一接口,所以無法同時提供兩個方法。這種情況下,Java會呈現衍生層的方法,而不是基層的方法。
這種機制叫做方法覆蓋(method overriding)。方法覆蓋可以被很好的利用,用於修改基類成員的方法。比如,在衍生層,也就是定義Woman時,可以修改基類提供的breath()方法:
class Woman extends Human {/** * new method */ public Human giveBirth() { System.out.println("Give birth"); return (new Human(20)); } /** * override Human.breath() */ public void breath() { super.breath(); System.out.println("su..."); } }注意,此時我們位於衍生層,依然可以通過super來調用基類對象的breath()方法。當我們外部調用Woman類時,由於方法覆蓋,就無法再調用基類對象的該方法了。
class Human { /** * constructor */ public Human(int h) { this.height = h; } /** * accessor */ public int getHeight() { return this.height; } /** * mutator */ public void growHeight(int h) { this.height = this.height + h; } /** * breath */ public void breath() { System.out.println("hu...hu..."); } private int height; }衍生類Woman類的定義及其構造方法:
class Woman extends Human { /** * constructor */ public Woman(int h) { super(h); // base class constructor System.out.println("Hello, Pandora!"); } /** * new method */ public Human giveBirth() { System.out.println("Give birth"); return (new Human(20)); } /** * override Human.breath() */ public void breath() { super.breath(); System.out.println("su..."); } }
extends
method overriding
protected
super.member, super()