書名: 從零開始學Java Web開發作者名: 孫更新等編著本章字數: 3515字更新時間: 2018-12-27 13:06:00
3.2 類的繼承性
繼承性是面向對象語言的又一個基本特性,它是一種由已有的類創建新類的機制,正是因為有這種機制,才使得面向對象語言編寫的程序代碼具有較高的復用性。
3.2.1 類的繼承
繼承是指相關的類之間的層次關系。利用繼承,可以先創建一個共有屬性的一般類,根據該一般類再創建具有特殊屬性的新類,新類繼承一般類的狀態和行為,并根據需要增加它自己的新的狀態和行為。
由繼承而得到的類稱為子類,被繼承的類稱為父類。類繼承的具體定義格式如下。
class subclassname extends superclassname{ … }
在上述類的聲明中,通過使用關鍵字extends來創建一個類的子類,其中,subclassname是聲明的子類的類名,superclassname是繼承的父類的類名。
在3.1.1小節中介紹過Java類的聲明,在當時定義的類并沒有使用extends關鍵字,那么是不是表示當時定義的類沒有繼承其他的類呢?答案是否定的。在Java語言中,Object類是所有類的祖先類,也就是說所有的類都是直接或者間接繼承Object類的。所以當沒有使用extends關鍵字時,就表示這個類只是Object類的子類。
說明
與C++語言中的繼承不同,Java語言不支持多重繼承,子類只能有一個父類。
【實例3-7】實現類的繼承。
首先定義父類Student。
01 class Student { 02 int stu_id; //父類的成員變量 03 void set_id(int id) { //父類的成員方法 04 stu_id=id; 05 } 06 void show_id() { //父類的成員方法 07 System.out.println(“The student_ID is:”+stu_id); 08 } 09 }
【代碼說明】該類中第2行定義一個int型的成員變量,第3行和第6行分別定義名稱為set_id和show_id()的兩個成員方法。
然后定義繼承Student類的子類Granduate類。
01 class Granduate extends Student { 02 int dep_number; //子類的成員變量 03 void set_dep(int dep_num) { //子類的成員方法 04 dep_number=dep_num; 05 } 06 void show_dep() { //子類的成員方法 07 System.out.println(“The department number is:”+dep_number); 08 } 09 }
【代碼說明】該類是Student類的子類,因此,它可以直接使用Student類中定義的成員變量和成員方法,而無須重復定義。除此之外,在第2行還定義了自己的成員變量dep_number,在第3行和第6行分別定義了成員方法set_dep()和show_dep()。
最后定義調用Granduate類的應用程序類。
01 public class Student_Show { 02 public static void main(String args[]) { 03 Granduate sun=new Granduate(); //創建子類對象 04 sun.set_id(102); //調用父類中定義的方法 05 sun.set_dep(6); //調用子類中定義的方法 06 sun.show_id(); //調用父類中定義的方法 07 sun.show_dep(); //調用子類中定義的方法 08 } 09 }
【代碼說明】在應用程序類中的第3行創建了Granduate類,第4行和第6行分別調用其父類中定義的成員方法,第5行和第7行則分別調用子類中定義的成員方法。
【運行結果】該程序的輸出結果如圖3.3所示。

圖3.3 程序運行結果
說明
根據類的繼承關系,創建的子類對象一定也是父類的對象。例如上例中創建的Granduate類的對象sun也是其父類Student類的對象,但是反過來,父類的對象則不一定是子類的對象。
3.2.2 方法的重載和覆蓋
當類之間出現繼承關系之后,在子類中就將可能出現成員方法的重載和覆蓋。這是面向對象編程中多態性的體現。
1.方法的覆蓋
如果在子類中定義了與父類中的成員方法同名的成員方法,那么當子類的對象在程序中調用該成員方法時,調用的將是子類中新定義的成員方法,而子類中繼承下來的父類中的成員方法就將被覆蓋掉了,如果要訪問被覆蓋的成員方法,則只有通過父類的對象來調用它了。
例如,在實例3-7中子類Granduate中定義的成員方法與其繼承的父類中的成員方法沒有同名,所以不存在方法的覆蓋。下面修改Granduate類的定義,具體代碼如下:
01 class Granduate extends Student { 02 int dep_number; 03 void set_dep(int dep_num) { 04 dep_number=dep_num; 05 } 06 //定義與父類中成員方法同名的方法,實現方法的覆蓋 07 void show_id() { 08 System.out.println(“The department number is:”+dep_number); 09 } 10 }
【代碼說明】 子類Granduate中的第7行,定義了一個與父類Student中成員方法同名的方法,這就是方法的覆蓋。
然后將調用Granduate類的應用程序類的代碼修改如下:
01 public class Student_Show { 02 public static void main(String args[]) { 03 Granduate sun=new Granduate(); 04 sun.set_id(102); 05 sun.set_dep(6); 06 //由于方法覆蓋,這里調用的將是子類中定義的show_id()方法 07 sun.show_id(); 08 } 09 }

圖3.4 調用子類中覆蓋后的方法
【代碼說明】在應用程序的第7 行通過子類實例調用了show_id()方法,由于在子類和父類中都定義了該方法,那么通過子類調用時將調用子類中定義的show_id()方法。
【運行結果】執行應用程序時,將調用子類中定義的show_id()方法,最終程序輸出結果如圖3.4所示。
2.方法的重載
如果在一個類中,定義了兩個或者兩個以上的具有不同參數列表的同名方法,這種情況就被稱為方法的重載,方法的重載體現了Java作為面向對象語言的多態性。
那么什么是不同的參數列表呢?僅僅是形參名稱不同的兩個參數列表實際上仍然是相同的,但如果是參數的個數或者參數的數據類型不同,則這樣的參數列表才是不同的。因此,同一個對象在調用具有相同名稱的方法時,會根據傳遞進來的參數的個數或數據類型的不同,而自動選擇對應的方法。
在類的繼承關系中,如果當子類中定義了與父類中同名的方法時,但是方法的參數列表不同,這時在子類中將對該方法進行重載,即子類中既繼承下來父類的方法,又定義了自己的新的成員方法,不會對父類的方法進行覆蓋。
下面修改實例3-7中的Granduate類的定義,具體代碼如下:
01 class Granduate extends Student { 02 int dep_number; 03 void set_dep(int dep_num) { 04 dep_number=dep_num; 05 } 06 //定義與父類中成員方法同名的方法,但參數列表不同 07 void show_id(String name) { 08 System.out.println(“The department number of”+name+” is:”+dep_number); 09 } 10 }
【代碼說明】在該類中的第7行定義了與父類中成員方法同名的方法,但是與父類中的show_id()方法的參數列表不同,因此實際上在Granduate類中將存在兩個名稱為show_id的成員方法,但是這兩個方法一個是參數為空,一個是具有一個字符串類型參數。
然后將調用Granduate類的應用程序類的代碼修改如下:
01 public class Student_Show { 02 public static void main(String args[]) { 03 Granduate sun=new Granduate(); 04 sun.set_id(102); 05 sun.set_dep(6); 06 //由于方法重載,這里調用子類中的兩個不同的show_id()方法 07 sun.show_id(); 08 sun.show_id(“sun”); 09 } 10 }
【代碼說明】在應用程序的第7 行和第8 行分別兩次調用了show_id()方法,但是這兩次調用的方法的參數不同,第7行將調用父類Student中定義的show_id()方法,第8行將調用子類Granduate中定義的show_id()方法。
【運行結果】執行應用程序時,將調用子類中重載的兩個不同的show_id()方法,最終程序輸出結果如圖3.5所示。

圖3.5 調用重載的方法
3.2.3 抽象類和最終類
在一般情況下,Java中的所有的類都可以被其他類繼承,也可以直接被實例化使用。但是有兩種特殊的類,一種是專門用來給其他類做父類的,自己不能夠被實例化,另一種是不能夠再被其他類所繼承的。這就是本小節要詳細介紹的抽象類和最終類。
1.抽象類
面向對象的編程思想使得開發者可以編寫模塊化的程序,然后用類似搭積木的方式來組織這些模塊以實現特定的功能。甚至可以對一個未定的功能預留一個模塊的位置,留待以后去實現。
將這種思想應用于類的定義中,對于某種未定的操作,可以在類中先只定義方法聲明,不定義方法實現,以后再在這個類的子類中去具體實現這個方法。這樣的方法被稱為抽象方法,而包含抽象方法的類就被稱為抽象類。
抽象方法和抽象類的定義方式都是在方法名和類名之前加上abstract關鍵字。其中,抽象方法的聲明與普通方法基本一致,也需要有方法名、參數列表和返回值類型,只是沒有方法體。類的成員方法的聲明格式如下:
[<修飾符>] abstract <返回類型> <方法名> ([<參數列表>]);
說明
在參數列表的括號后面一定要加上分號。
抽象類由于包含抽象方法,因此它不能使用new關鍵字進行實例化,也不能在類中定義構造函數和靜態方法,但是抽象類中可以包含具體實現了的非靜態方法。所以讀者不要誤解成抽象類中的方法全部都是抽象方法,而是只有一個類中包含一個抽象方法,那么這個類就是抽象類。抽象類的子類中應該對抽象方法進行具體的實現,否則子類本身也就成為抽象類了。
【實例3-8】抽象類的定義和使用。
首先定義抽象類Student,該類中包含一個抽象方法show_id()。
01 abstract class Student { 02 int stu_id; 03 void set_id(int id) { 04 stu_id=id; 05 } 06 //定義抽象方法 07 abstract void show_id(); 08 }
【代碼說明】在該抽象類的第7行定義了一個抽象方法show_id()。
然后定義繼承Student類的子類Granduate類。
01 class Granduate extends Student { 02 int dep_number; 03 void set_dep(int dep_num) { 04 dep_number=dep_num; 05 } 06 //具體定義父類中的抽象方法 07 void show_id() { 08 System.out.println(“The department number is:”+dep_number); 09 } 10 }
【代碼說明】在該類的第7行定義了父類中的抽象方法show_id()的具體實現。
說明
必須具體定義父類中的抽象方法,否則該類也將成為抽象類。
最后定義調用Granduate類的應用程序類。
01 public class Student_Show { 02 public static void main(String args[]) { 03 Granduate sun=new Granduate();//創建子類的實例 04 sun.set_id(102); 05 sun.set_dep(6); 06 sun.show_id();//調用子類中已經實現了的父類中的抽象方法 07 } 08 }
【代碼說明】在應用程序類的第6行調用show_id()方法,由于該方法已經在Granduate類中被實現,所以可以被正確調用。
【運行結果】執行應用程序,將調用子類中具體定義后的show_id()方法,最終程序輸出結果與如圖3.4所示的結果一樣。
2.最終類
如果在一個類定義前加上final關鍵字,那么這個類就不能再被其他的類所繼承,這樣的類被稱做最終類。最終類可以避免開發者編寫的類被別人繼承后加以修改。
例如,下面的類定義和繼承:
final class A { ….. } class B extends A { …. }
這樣的繼承將是錯誤的,在程序編譯時將提示類A是不能被繼承的。
final關鍵字除了可以用在聲明最終類時,還可以應用在成員變量和成員方法的聲明上,如果在成員變量的定義前加上final關鍵字,則這個變量的值在以后的程序中只能被引用,而不能被改變,final在這里的作用相當于C++語言中的const。
說明
使用final的成員變量必須在聲明時同時給定初始值。
例如,聲明不能被修改的成員變量的示例代碼如下:
class A { final float PI=3.14159f; final float E=2.71828f; …. }
如果在成員方法定義前加上final關鍵字,那么表示這個方法在子類中不能被覆蓋。