1. 程式人生 > 實用技巧 >Java面向物件程式設計-繼承

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 比較組合與繼承

組合關係 繼承關係
區域性類 父類
整體類 子類
從整體類到區域性類的分解過程 從子類到父類的抽象過程
從區域性類到整體類的組合過程 從父類到子類的擴充套件過程