Java 三大特性之——继承
繼承(inheritance)是面向對象的重要概念。繼承是除組合(composition)之外,提高代碼重復可用性(reusibility)的另一種重要方式。我們在組合(composition)中看到,組合是重復調用對象的功能接口。我們將看到,繼承可以重復利用已有的類的定義。
類的繼承
? ? ? ? 我們之前定義類的時候,都是從頭開始,詳細的定義該類的每一個成員。比如下面的Human類:
[java]?view plaincopy
現在要定義一個新的類,比如Woman類,并假設Woman與Human類相當類似:
? ? ? ? ?Human & Woman
我們可以像以前一樣,從頭開始,完整的定義Woman類:
[java]?view plaincopy一個程序員在寫上面程序的時候,會有很大的煩惱。許多定義都曾在Human類中寫過,但我們還要重新敲一遍。Woman類只新增了一個giveBirth()方法 (該方法創建并返回一個新的Human對象)。
利用繼承,我們可以避免上面的重復。讓Woman類繼承自Human類,Woman類就自動擁有了Human類中所有public成員的功能。
我們用extends關鍵字表示繼承:
[java]?view plaincopy
?
可以用以下Test類測試:
[java]?view plaincopy
通過繼承,我們創建了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在許多情況下可以自動識別成員的歸屬。但我覺得這是個好習慣。)
2、protected
? 我們之前介紹了兩個訪問權限相關的關鍵字,private和public,它們控制了成員的外部可見性。現在,我們介紹一個新的訪問權限關鍵字: protected。
標為protected的成員在該類及其衍生類中可見。這個概念很容易理解,就是說,基類的protected成員可以被衍生層訪問,但不能被外部訪問,如下圖:
3、方法覆蓋
? 衍生類對象的外部接口最終由基類對象的public成員和衍生層的public成員共同構成。如果基類public成員和衍生層的public成員同名,Java接口中呈現的究竟是哪一個呢?
? 我們在構造方法與方法重載中已經提到,Java是同時通過方法名和參數列表來判斷所要調用的方法的。方法是由方法名和參數列表共同決定的。上述問題中,如果只是方法名相同,而參數列表不同,那么兩個方法會同時呈現到接口,不會給我們造成困擾。外部調用時,Java會根據提供的參數,來決定使用哪個方法(方法重載)。
? 如果方法名和參數列表都相同呢? 在衍生層時,我們還可以通過super和this來確定是哪一個方法。而在外部時,我們呈現的只是統一接口,所以無法同時提供兩個方法。這種情況下,Java會呈現衍生層的方法,而不是基層的方法。
這種機制叫做方法覆蓋(method overriding)。方法覆蓋可以被很好的利用,用于修改基類成員的方法。比如,在衍生層,也就是定義Woman時,可以修改基類提供的breath()方法:
注意,此時我們位于衍生層,依然可以通過super來調用基類對象的breath()方法。當我們外部調用Woman類時,由于方法覆蓋,就無法再調用基類對象的該方法了。
方法覆蓋保持了基類對象的接口,而采用了衍生層的實現。使用時要注意一下三點:
1、三同原則:同方法名、同返回類型、同參數表
2、子類中的覆蓋方法不能使用比父類中被覆蓋的方法更嚴格的訪問權限。
3、如需使用父類中原有的方法,可使用super關鍵字,該關鍵字引用了當前類父類的方法。
覆蓋實際上是父子類之間的關系。
4、構造方法
? ?在了解了基類對象和衍生層的概念之后,衍生類的構造方法也比較容易理解。
? ?我們要在衍生類的定義中定義與類同名的構造方法。在該構造方法中:
1)由于在創建衍生對象的時候,基類對象先被創建和初始化,所以,基類的構造方法應該先被調用。我們可以使用super(argument list)的語句,來調用基類的構造方法。
2)基類對象創建之后,開始構建衍生層 (初始化衍生層成員)。這和一般的構建方法相同,參考構造方法與方法重載
比如下面的程序中,Human類有一個構造方法:
[java]?view plaincopy
java的繼承覆蓋總結
1.構造函數:
當子類繼承一個父類時,構造子類時需要調用父類的構造函數,存在三種情況
1)父類無構造函數或者一個無參數構造函數,子類若無構造函數或者有無參數構造函數,子類構造函數中不需要顯式調用父類的構造函數,系統會自動在調用子類構造函數前調用父類的構造函數
2)父類只有有參數構造函數,子類在構造方法中必須要顯示調用父類的構造函數,否則編譯出錯
3)父類既有無參數構造函數,也有有參構造函數,子類可以不在構造方法中調用父類的構造函數,這時使用的是父類的無參數構造函數? ? ? ? ?
2.方法覆蓋:
1)子類覆蓋父類的方法,必須有同樣的參數返回類型,否則編譯不能通過
2)子類覆蓋父類的方法,在jdk1.5后,參數返回類可以是父類方法返回類的子類
3)子類覆蓋父類方法,可以修改方法作用域修飾符,但只能把方法的作用域放大,而不能把public修改為private
4)子類方法能夠訪問父類的protected作用域成員,不能夠訪問默認的作用域成員
5)子類的靜態方法不能隱藏同名的父類實例方法
6)java與C++一樣,繼承的方法具有多態性
? ? ? ? ?//以上6個結論已經過代碼驗證
3.成員覆蓋:
1)當子類覆蓋父類的成員變量時,父類方法使用的是父類的成員變量,子類方法使用的是子類的成員變量
? 這個聽起來很容易理解的一回事,但是實際使用過程中很多人容易搞混:尤其是在多態的時候,調用一個被繼承的方法,該方法訪問是一個被覆蓋的成員m,那么方法中到底是訪問了父類的成員還是子類的成員m?結論是,若實際調用的是父類的方法,就使用了父類的該成員m,若實際調用的是子類的方法,就使用子類的成員m,記住一句,每個類使用成員都相當于在前面加了 一個this指針。
總結例子:
[java]?view plaincopy
1
21
21
總結
以上是生活随笔為你收集整理的Java 三大特性之——继承的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android loading进度条使用
- 下一篇: Javascript基础(一)