Java面向物件程式設計-繼承
第六章 繼承
6.1 繼承的基本語法
在Java語言中,用extends關鍵字來表示一個類繼承了另一個類。
public class Sub extends Base{
...
}
以上的程式碼表明Sub類繼承類Base類。
- 當Sub類和Base類位於同一個包中時,Sub類繼承Base類中public、protected和預設訪問級別的成員變數和成員方法。
- 當Sub類和Base類位於不同的包中時,Sub類繼承Base類中public、protected訪問級別的成員變數和成員方法。
Java語言不支援多繼承,即一個類只能直接繼承一個類。
儘管一個類只能有一個直接父類,但是它可以擁有多個間接的父類。
所有的Java類都直接或間接的繼承了java.lang.Obiect類,Object類是所有Java類的祖先。在這個類中定義了所有的Java物件都具有的相同行為。
6.2 方法過載
有時候,類的同一種功能有多種實現方式,到底採用哪種實現方式,取決於呼叫者給定的引數。
對於類的方法(包括從父類繼承的方法),如果有兩個方法的方法名相同,但引數不一致,那麼可以說,一個方法是另一個方法的過載。
過載方法必須滿足以下條件:
- 方法名相同
- 方法的引數型別、個數、順序至少有一項不同。
- 方法的返回型別可以不同。
- 方法的修飾符可以不相同。
在一個類中,不允許定義兩個方法名相同,並且引數簽名也相同的方法。
6.3 方法覆蓋
如果在子類中定義的一個方法,其名稱、返回型別及引數簽名正好與父類中的某個方法的名稱、返回型別及引數簽名相匹配,那麼可以說,子類的方法覆蓋了父類的方法。
覆蓋必須滿足多種約束:
- 子類的名稱、引數簽名和返回型別必須與父類的名稱、引數簽名和返回型別一致。
- 子類方法不能縮小父類方法的訪問許可權。
- 子類方法不能丟擲比父類方法更多的異常。
- 方法覆蓋只存在於子類和父類(包括直接父類和間接父類)之間。
- 父類的靜態方法不能被子類覆蓋為非靜態方法。
- 子類可以定義同父類的靜態方法同名的靜態方法,以便在子類中隱藏父類的靜態方法。
- 父類的非靜態方法不能被子類覆蓋為靜態方法。
- 父類的私有方法不能被子類覆蓋。
- 父類的抽象方法可以被子類通過兩種途徑覆蓋:一是子類實現父類的抽象方法,二是子類重新宣告父類的抽象方法。
- 父類的非抽象方法可以被覆蓋為抽象方法。
6.4 方法覆蓋與方法過載的異同
方法覆蓋和方法過載具有以下相同點:
- 都要求方法同名
- 都可以用於抽象方法和非抽象方法之間
方法覆蓋和方法過載具有以下不同點:
- 方法覆蓋要求引數簽名必須一致,而方法過載要求引數簽名必須不一致。
- 方法覆蓋要求返回型別必須一致,而方法過載對此不作限制。
- 方法覆蓋只能用於子類覆蓋父類的方法,方法過載用於同一個類的所有方法(包括從父類中繼承而來的方法)
- 方法覆蓋對方法的訪問許可權和丟擲的異常有特殊的要求,而方法過載在這方面沒有任何限制。
- 父類的一個方法只能被子類覆蓋一次,而一個方法在所在的類中可以被過載多次。
6.5 super關鍵字
super和this關鍵字都可以用來覆蓋Java預設作用域,使被遮蔽的方法或變數變為可見。
在以下場合會出現方法或變數被遮蔽的現象:
- 在一個方法內,當局部變數和類的成員變數同名,或者區域性變數和父類的成員變數同名時,按照變數的作用域規則,只有區域性變數在方法內可見。
- 當子類的某個方法覆蓋了父類的一個方法時,在子類的範圍內,父類的方法不可見。
- 當子類中定義類和父類同名的成員變數時,在子類的範圍內,父類的成員變數不可見。
注意:如果父類的成員變數和方法被定義為private型別,那麼子類永遠無法訪問它們。
在程式中,super關鍵字在以下情況下會被使用:
- 在類的構造方法中,通過super語句呼叫這個類的父類的構造方法
- 在子類中訪問父類的被遮蔽的方法和屬性。
注意:只能在構造方法或例項方法內使用super關鍵字,而在靜態方法和靜態程式碼塊中不能使用super關鍵字
6.6 多型
當系統A訪問系統B的服務時,系統B可以通過多種實現方式來提供服務,而這一切對系統A是透明的。
Java語言允許某個型別的引用變數引用子類的例項,而且可以對這個引用變數進行型別轉換。
如果把引用變數轉換為子類型別,稱為向下轉型;如果把引用變數轉換為父類型別,稱為向上轉型。
多型的特性:
- 對於一個引用型別的變數,Java編譯器按照它宣告的型別來處理。
- Java編譯器允許具有直接或間接繼承關係的類之間進行型別轉換,對於向上轉型,不必使用強制型別轉換;對於向下轉型,必須使用強制型別轉換。
- 假如兩種型別之間沒有繼承關係,即不在繼承樹的同一個繼承分支上,那麼Java編譯器不允許進行型別轉換。
- 對於一個引用型別的變數,執行時Java虛擬機器按照它實際引用的物件來處理。
- 在執行時,子類的物件可以轉換為父類型別,而父類的型別實際上無法轉換為子類型別。
- 在執行時環境中,通過引用型別變數來訪問所引用物件的方法和屬性時,Java虛擬機器採用以下繫結規則:
- 例項方法與引用變數實際引用的物件的方法繫結,這種繫結屬於動態繫結,因為是在執行時由Java虛擬機器動態決定的。
- 靜態方法與引用變數所宣告的型別的方法繫結,這種繫結屬於靜態繫結,因為實際上在編譯階段就已經做了繫結。
- 成員變數(包括靜態變數和例項變數)與引用變數所宣告的型別的成員變數繫結,這種繫結屬於靜態繫結,因為實際上在編譯階段就已經做了繫結。
6.7 繼承的利弊和使用原則
6.7.1 繼承樹的層次不可太多
繼承樹的層次(不考慮頂層的Object類)應當儘量保持在2~3層。如果繼承樹的層次很多會導致以下弊端:
- 物件模型的結構太複雜,難以理解,增加了設計和開發的難度。
- 影響系統的可擴充套件性。
6.7.2 繼承樹的上層為抽象層
當一個系統使用一棵繼承樹上的類時,應該儘可能把引用變數宣告為繼承樹的上層型別,這可以提高兩個系統之間的鬆耦性。
位於繼承樹上層的類具有以下作用:
- 定義了下層子類都擁有的相同屬性和方法,並且儘可能地為多數方法提供預設的實現,從而提高程式程式碼的可重用性。
- 代表系統的介面,描述系統所能提供的服務。
6.7.3 繼承關係最大的弱點:打破封裝
繼承關係最大的弱點就是打破了封裝。增加了維護軟體的工作量。還會導致父類的實現細節被子類惡意篡改的危險。
6.7.4 精心設計專門用於被繼承的類
- 對這些類必須提供良好的文件說明,使得建立該類的開發人員知道如何正確安全的擴充套件它。
- 儘可能封裝父類的實現細節,也就是把代表實現細節的屬性和方法定義為private型別。
- 把不允許子類覆蓋的方法定義為final型別。
- 父類的構造方法不允許呼叫可被子類覆蓋的方法。
- 如果某些類不是專門為了繼承而設計的,那麼隨意繼承它們是不安全的,可以採用以下兩種措施來禁止繼承。
- 把類宣告為final型別。
- 把這個類所有構造方法宣告為private型別,然後通過一些靜態方法來負責構造自身的例項。
6.7.5 區分物件的屬性與繼承
6.8 比較組合與繼承
組合關係 | 繼承關係 |
---|---|
區域性類 | 父類 |
整體類 | 子類 |
從整體類到區域性類的分解過程 | 從子類到父類的抽象過程 |
從區域性類到整體類的組合過程 | 從父類到子類的擴充套件過程 |