官术网_书友最值得收藏!

4.3 類的繼承

繼承是面向對象程序設計技術的又一基本特征,Java語言通過繼承機制構建了類的層次結構,實現代碼的復用。

4.3.1 繼承的概念

Java語言基于對象之間的相似性構建其類型——類,Java程序通過類的定義對所涉及的對象進行了分類。事實上,一些類之間具有一定程度的相似性。例如,2.5節使用過的簡單Swing GUI組件窗口、標簽、文本字段、按鈕、“口令”字段和單選按鈕等都具有位置、大小、前景顏色、背景顏色等共同屬性,也都具有定位——setLocation(int x, int y)、設置和改變大小——setSize(int width, int height)、顯示——setVisible(boolean b)、設置和改變前景顏色——setForeground(Color c)及設置和改變背景顏色——setBackground(Color c)等共同方法,因此可以從定義這些組件的類JFrame、JLabel、JTextField、JButton、JPasswordField和JRadioButton中抽取它們共同的屬性和方法形成一個新的類定義——Component。更為重要的是定義了Component之后,原來那些類中就不必定義這些共同的屬性和方法,把Component作為它們的基礎類(就稱為基類),把它們自身作為導出類只定義基類中沒有的屬性和方法。顯然,這種組織代碼的方法可以使多個導出類共享基類的代碼,從而實現了基類代碼的復用,使整個程序的代碼更加簡潔。

Java語言提供了繼承機制,使程序從已有的類中導出(也稱為派生)新的類,新的類吸收(稱為繼承)了已有類的屬性和方法,同時可以添加在已有類中沒有的屬性和方法擴展新類。在這種情況下,已有的類稱為父類(也稱基類、超類),派生出的新類稱為子類(也稱派生類)。

Java語言采用單繼承機制,即一個子類只能有一個父類,但是一個父類可以有多個子類。當然,一個類的子類也可以是另一個類的父類。例如,B類是A類的子類,B類還可以是C類的父類。在UML中用箭頭()表示類的繼承關系,箭頭指向父類,箭尾指向子類。上面三個類的繼承關系可以標記為ABC,此時,B類繼承了A類的屬性和方法,C類繼承了B類的屬性和方法,同時繼承了B類從A類中繼承來的屬性和方法。

Java語言的類通過繼承構建了一個單根樹型層次結構,樹的根是Object類。也就是說,Java中的所有類都是Object類的直接或間接派生類。圖4.9是Swing庫的主要GUI組件繼承層次圖。

圖4.9 Swing組件繼承層次圖

4.3.2 子類的創建

Java語言使用關鍵字extends創建已有類的子類?;靖袷饺缦拢?/p>

     class 子類名 extends 父類名 {
           … // 子類添加的屬性定義
           … // 子類添加的方法定義
           … // 對父類方法的重新定義
     }

例如,2.1節創建的簡單加法計算器程序窗口NumberAddition類就是javax.swing.JFrame類的子類,生成的代碼是“public class NumberAddition extends javax.swing.JFrame{…}”(見圖4.10)。又如,例2.7設計的用戶登錄程序的UserLogin窗口也是javax.swing.JFrame類的子類,生成的代碼是“public class UserLogin extends javax.swing.JFrame {…}”。應用程序的頂層窗口具有大量的共同屬性和行為,在javax.swing.JFrame類中已經有了良好的定義,但不同的應用程序窗口內包含的組件和程序的具體行為是有差別的,因此應該定義為javax.swing.JFrame類的子類。

圖4.10 簡單加法計算器程序的窗口類NumberAddition的定義

如果一個類的直接父類就是java.lang.Object,則不需要指出父類而直接定義即可,也就是不需要寫出“extends Object”。

4.3.3 派生類對基類成員的訪問性

當創建了一個類的子類后,子類就繼承了父類的所有屬性和方法。但是,在子類中對繼承來的成員的訪問性取決于父類成員的訪問修飾限定:正如表4.1所示,子類可以直接訪問父類的public成員、同一個包中的默認訪問性成員以及不同包中的protected成員,但不能訪問父類中的private成員。

例如,對于繼承關系為ABC的三個類,如果采用如下定義:

程序清單4.2:

程序清單4.3:

在NetBeans IDE的項目窗口右擊項目chap04|“源包”|book.inheritdemos下的TestABC.java文件,在快捷菜單中單擊“編譯文件”菜單項,再次右擊該文件,執行“運行文件”菜單項,程序輸出如圖4.11所示。

圖4.11 運行TestABC.java文件的輸出

類C是類A的派生類且在不同的包中,A類的成員變量x采用protected修飾,在類C中可以直接訪問變量x——程序清單4.2的第18行;類C是類B的子類且在不同的包中,B類的成員變量y采用默認修飾具有包訪問性,如果C中直接訪問變量y,則會發生錯誤,如程序清單4.2的第20行;在類C中可以訪問其內部定義的private變量z——程序清單4.2的第23行。TestABC類與A類和B類處于同一個包中,與C類處于不同的包,且與A類、B類和C類沒有繼承關系,TestABC類可以訪問的A類、B類和C類的成員如圖4.12的彈出式提示框所示,程序清單4.3中第22行能夠直接訪問A類定義的protected成員變量x。值得注意的是,TestABC類中不能直接訪問B類中定義的包訪問性成員變量y(見圖4.12的彈出式提示框),原因在于objc是C類對象,objc.y是C類繼承自父類B的成員變量,由于TestABC類與C類不在同一個包中,自然就不能訪問C類對象的包訪問性成員變量objc.y。如果在TestABC類中創建一個B類的對象,由于這兩個類位于同一個包中,因此可以直接訪問具有包訪問性的成員變量objb.y(見圖4.12的右邊彈出式提示框)。

圖4.12 TestABC類對A類、B類和C類成員的訪問性示例

4.3.4 抽象方法與抽象類

許多情況下,基類中的一些方法只是為其派生類提供一個統一的調用接口——統一的方法名和參數表定義,方法體中具體的實現代碼在其不同的子類中各不相同,基類中無法給出此類方法的具體實現代碼,Java語言提供了關鍵字abstract在程序中把此類方法定義為抽象方法。抽象方法只有方法頭而沒有方法體,方法頭定義了返回值類型、方法名和參數表,定義形式如下:

     [訪問修飾符] abstract 返回值類型 方法名(參數表);

其中,[訪問修飾符]可以是public、protected和private,如果沒有此項則為默認的包訪問性;返回值類型可以是任意合法的基本數據類型和引用類型,取決于方法的處理邏輯;參數表是“參數類型 參數名,參數類型 參數名,…”格式,也可以只給出參數類型而省略參數名,也可以是空的參數表——括號內什么都不寫。應特別注意,參數表的閉括號“)”之后是語句結束符分號“;”。

如果一個類中包含抽象方法,該類就是抽象類,在類定義時頭部加上abstract關鍵字。抽象類的定義形式如下:

     [訪問修飾符]  abstract  class  類名 [extends 父類名] {
         // 屬性定義,可選
         // 方法定義,可選
         // 抽象方法定義,可選
     }

Java程序中不能創建抽象類的對象。如果在類中沒有抽象方法而定義為抽象類,則目的就是禁止創建該類的對象。

例如,平面圓、矩形、三角形都屬于平面幾何形狀,都具有坐標位置等屬性,都可以計算面積和周長。因此,可以先定義一個平面幾何形狀類MyShape,而將它們分別定義為MyShape的子類。不同具體幾何形的面積和周長計算方法是不相同的,在MyShape類中難以具體計算,因此可以將MyShape類定義為抽象類,只在其中定義面積和周長計算方法的方法頭,而方法的具體實現留到子類完成。這些類的定義如程序清單4.4~4.7所示。

程序清單4.4:

程序清單4.5:

程序清單4.6:

程序清單4.7:

在程序清單4.4中所定義的抽象類MyShape中包含抽象方法getArea()和getPerimeter(),其子類(如MyCircle類,見程序清單4.5)中必須實現繼承的所有抽象方法,NetBeans IDE中會有提示(見圖4.13),單擊子類定義行前面的圖標,會給出有關建議(見圖4.14)。

如果子類中沒有實現所有的抽象方法,則該子類也必須指定為抽象類。

圖4.13 NetBeans IDE抽象類的子類定義提示

圖4.14 NetBeans IDE抽象類的子類實現建議

程序清單4.5~4.7所定義的子類MyCircle、MyRectangle和MyTriangle中所實現的getArea()和getPerimeter()各不相同,但是這些類的對象對這兩個方法的調用具有相同的代碼。如計算面積可以采用如圖4.15所示的代碼調用——見圖4.15的第18和19行、23和24行、28和29行。該程序的運行輸出結果見圖4.16,可見程序能正確運行。

圖4.15 子類對getArea()和getPerimeter()方法的調用

圖4.16 平面圓、矩形和三角形的面積和周長計算程序輸出

4.3.5 子類的構造方法

子類與其直接父類在構造方法方面存在約束關系,即子類的構造方法必須調用其父類的構造方法,且必須是子類構造方法體中的第一條語句,調用是使用Java語言關鍵字super實現的,調用格式如下:

     super(實參表) ;

其中,實參表是傳遞給父類構造方法的參數,多個參數使用“,”分隔。此實參表必須與父類的某個構造方法形參表匹配。如果子類的構造方法沒有對父類構造方法的調用語句,而父類又存在不含參數的構造方法,編譯器會給子類構造方法隱式添加對其父類無參數構造方法的調用語句——super()。

在程序清單4.4中定義的MyShape類中由于其構造方法帶有參數,定義它的子類時,NetBeans IDE會提示在子類中添加構造函數(見圖4.17),且選取該建議之后會自動創建調用父類構造方法的語句。例如,為MyTriangle類生成構造方法并生成語句“super(x, y);”(見程序清單4.7)。

圖4.17 NetBeans IDE提示添加構造函數以定義其父類構造方法

super關鍵字還用于訪問父類的屬性和方法。如果子類的某個屬性名與父類的相同,可以使用“super.屬性名”在子類中引用父類的這個屬性;如果父類的某個方法被子類覆蓋,可以使用“super.方法名(實參表)”在子類中調用父類的這個方法。

this是Java語言中非常常用的一個關鍵字,它的基本含義就是當前對象的一個引用。就好像張三跟好多人談話,使用“我”指代他自身一樣,this在其所處的程序之中就相當于指代詞“我”。在前面多次見到this的應用,通常有下面幾種用法。

(1)在局部變量與實例變量同名的情況下,使用this引用當前對象的實例變量。例如,程序清單4.1中User類構造方法的形參與實例變量同名,因此自動生成的方法體中在賦值號左邊通過this引用實例變量,賦值號右邊引用的是形參變量,語句形如“this.name =name;”。

(2)類中存在多個構造方法時,在一個構造方法中可以通過this引用其他的構造方法,且this引用的構造方法是其方法體的第一條語句。例如,程序清單4.1中定義的用戶類User,如果規定每個用戶的用戶名是唯一的,就可以定義如下構造方法。

     public User(String name) {
         this.name = name;
     }

該程序中原來定義的構造方法可以修改為以下定義。

     public User(String name, String password, int job) {
         this(name );
         this.password = password;
         this.job = job;
     }

(3)使用this引用當前對象。例如,以下程序:

第9行語句中的調用obj.getObjA(),此時obj是當前對象,它的方法getObjA()中的this引用的對象就是obj自身。

如果在Aclass類中再定義一個方法methodB(),可以在其中使用this調用getObjA()方法,例如:void methodB() { this.getObjA(); }。

在不出現二義性的情況下可以省略this引用,這正如我們日常說話在許多情況下可以省略“我”一樣。在NetBeans IDE中如果輸入this.可以出現代碼幫助窗口,從而可以獲得幫助。

4.3.6 方法的覆蓋與final方法及final類

對于父類中定義的非抽象方法,在其子類中也可以重新實現,即保持該方法的頭部在父類中的定義不變,重新編寫與父類不同的方法體代碼,這就是方法的覆蓋(Override,也稱為重寫)。例如,如果將程序清單4.4所定義的MyShape類中的方法getArea()方法定義為:

     public double getArea() {
          return 0.0;
     }

則程序清單4.5~4.7所定義的其子類MyCircle、MyRectangle和MyTriangle中所實現的getArea()方法與其保持了相同的方法頭——具有相同的方法名、參數表和返回值類型,但是具有不同的方法體代碼,因此這三個子類覆蓋了父類MyShape中的getArea()方法。

覆蓋的方法在子類和父類中必須具有相同的方法名和參數表;返回值類型與父類中保持相同或是其子類型;可訪問性也與父類保持相同或更加公開,例如,父類中方法是protected訪問性,子類中可以是protected或public。子類不能覆蓋父類的private方法,如果子類中出現了與父類同名的private方法,也只是子類的一個新方法,即使參數表和返回值類型相同,也與父類的同名方法無關。

Java語言中可以使用final關鍵字拒絕對一個方法的覆蓋,就是在定義一個方法時在方法頭部添加final修飾符,這種方法稱為最終方法。定義形式如下:

     [訪問修飾符] final 返回值類型 方法名(參數表) {
          …
     }

定義類時可以在頭部添加final修飾符,這種類稱為最終類。最終類的定義形式如下:

     final class 類名 [extends 父類名]  {
         …
     }

final類不能作為父類而派生子類,其中的所有方法都不可能被覆蓋。

final關鍵字與abstract關鍵字是互斥的,就是定義方法和類的時候如果使用了其中一個就不能使用另外一個。

主站蜘蛛池模板: 安福县| 南汇区| 康平县| 牟定县| 剑河县| 滦平县| 广汉市| 微山县| 明光市| 射阳县| 澳门| 武鸣县| 和硕县| 永宁县| 临夏市| 平原县| 乐东| 福海县| 砚山县| 二连浩特市| 香港| 广德县| 民丰县| 会同县| 海淀区| 光山县| 镇巴县| 乌兰察布市| 蕉岭县| 玉屏| 宿迁市| 从江县| 宣城市| 三明市| 社旗县| 独山县| 兴海县| 峨山| 平遥县| 宿迁市| 瑞安市|