繼承(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指涉基類物件)。
藍色部分為衍生性物件新增的內容,我將這部分稱為衍生層。藍色和黃色部分共同構成衍生性物件。衍生層的成員可以互相存取(Woman定義中的this)。更進一步,我們也可以訪問基層中public的成員。為此,我們用super關鍵字來指涉基底類別對象,使用super.member的方式來表示基底層的(public)成員。
當我們位於衍生層時(也就是在定義Woman類別時),就不能存取紅色的基層private成員。當我們位於外部時,既不能存取紫色的衍生層private成員,也不能存取紅色的基層private成員。
(衍生層的private成員有訪問禁忌,所以標為斜線。基層的private成員訪問禁忌最多,所以標為交叉斜線)
super和this類似,也是隱式參數。我們在類別定義的不同層次時,this會有不同的意義。要小心的使用this和super關鍵字。
(Java並不會強制使用this和super。Java在許多情況下可以自動識別成員的歸屬。但我覺得這是個好習慣。)
protected
我們之前介紹了兩個存取權限相關的關鍵字,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類別時,由於方法覆蓋,就無法再呼叫基底類別物件的該方法了。
方法覆蓋保持了基類物件的接口,而採用了衍生層的實作。
構造方法
在了解了基底類別物件和衍生層的概念之後,衍生類別的建構方法也比較容易理解。
我們要在衍生類別的定義中定義與類別同名的建構方法。在這個構造方法中:
1.由於在創建衍生對象的時候,基類物件先被創建和初始化,所以,基類的構造方法應該先被呼叫。我們可以使用super(argument list)的語句,來呼叫基底類別的建構方法。
2.基底類別物件建立之後,開始建構衍生層(初始化衍生層成員)。這和一般的建構方法相同,參考構造方法與方法重載
例如下面的程式中,Human類別有一個構造方法:
複製代碼代碼如下:
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()