《JAVA語言程序設(shè)計教程》課件第4章_第1頁
《JAVA語言程序設(shè)計教程》課件第4章_第2頁
《JAVA語言程序設(shè)計教程》課件第4章_第3頁
《JAVA語言程序設(shè)計教程》課件第4章_第4頁
《JAVA語言程序設(shè)計教程》課件第4章_第5頁
已閱讀5頁,還剩78頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

第4章繼承與接口4.1子類與父類4.2子類對象的構(gòu)造過程4.3成員變量隱藏與方法覆蓋4.4super關(guān)鍵字4.5上轉(zhuǎn)型對象4.6抽象類4.7接口4.8接口的回調(diào)

4.1子?類?與?父?類

在Java中,某個類可以從其他類中擴展出來,這種從另一個類中派生出來的類稱之為子類。而被子類所派生的類稱之為超類或父類。繼承是一種從已有的類中創(chuàng)建新類的機制。利用繼承,可以先創(chuàng)建一個包含有公共屬性的抽象一般類,然后再在這個抽象一般類的基礎(chǔ)上創(chuàng)建具有某些特殊屬性的子類。子類繼承了一般類的狀態(tài)和行為,然后再根據(jù)需要增加它自己新的狀態(tài)和行為。在Java中,除了所有類的公共父類Object以外,其他的類都有且只有一個直接的父類。這是因為Java是一種支持單繼承的語言,在Java中,一個類不能擁有兩個或兩個以上的直接父類。在沒有任何其他顯式聲明的直接父類的情況下,一個類被默認(rèn)為是Object類的子類。一個類的父類可以從另一個類中派生出來,正如同人類的族譜一樣。如果我們追尋一個類的繼承結(jié)構(gòu),可以一直追尋這個類的父親,父親的父親,也即這個類的祖父……一直延伸下去,最終我們會發(fā)現(xiàn)繼承的鏈條會一直延伸到所有類的公共父類Object。無論子類位于哪個包中,都將會繼承其父類的所有public和protected的成員變量和成員方法。而如果子類和父類位于同一個包,它還會繼承父類的友好成員變量和友好成員方法。值得注意的是,子類是不能繼承父類的初始化方法的。關(guān)于初始化方法在類繼承中如何使用,我們將在后續(xù)章節(jié)中討論。在類的聲明中,使用關(guān)鍵字extends來聲明一個類是另外一個類的子類,例如:

classSonextendsFather{

}

聲明了類Son是Father類的子類。如果一個類的聲明中沒有使用關(guān)鍵字extends,這個類將會被默認(rèn)是所有類的公共父類Object的子類。

【例4-1】類繼承的例子。在這個例子里面,類Son是類Father的子類,而類GrandSon是類Son的子類。Father類中的成員變量house_1_area記錄了父親擁有的房子的面積;Son類中的成員變量house_2_area記錄了兒子擁有的房子的面積;Grandson類中的成員變量house_3_area記錄了孫子擁有的房子的面積。在測試類ExtendTest中,我們新建了一個GrandSon類的實例gs,通過gs,我們分別調(diào)用了屬于父親的print_house_1_area方法、屬于兒子的print_house_2_area方法以及屬于孫子他自己的print_house_3_area方法,把祖孫三代的屋子的面積都打印了出來。注意GrandSon類中只聲明了print_house_3_area方法,其余兩個方法都是從Father類和Son類那里繼承下來的。在這個例子中我們用了另外一個關(guān)鍵字super,關(guān)于super我們將在后續(xù)章節(jié)加以說明。程序運行結(jié)果如圖4.1所示。圖4.1例4-1的運行結(jié)果

4.2子類對象的構(gòu)造過程

在例4-1中,我們看到子類GrandSon的對象實例不僅擁有他自己的成員變量house_3_area,也繼承了直接父類Son和祖先類Father的成員變量house_2_area和house_1_area。那么這些成員變量所擁有的內(nèi)存空間是如何分配的呢?當(dāng)用子類的初始化方法創(chuàng)建一個子類的實例對象時,這個子類聲明的所有成員變量都被分配了內(nèi)存空間,它的直接父類和所有的祖先類的成員變量也都被分配了內(nèi)存空間。但是正如我們前面所提到的,即使在同一個包中,子類也不能繼承父類對象中那些私有的成員變量和成員方法。那么是否意味著一個子類的直接父類和所有的祖先類中的私有成員變量,雖然被分配了內(nèi)存空間,但是子類卻無法使用它們呢?實際情況并非如此。盡管子類實例對象無法使用那些父類中的私有成員變量,但是它卻可以通過調(diào)用從父類繼承下來的方法,間接訪問那些父類中的私有成員變量。

【例4-2】在這個例子中,子類通過調(diào)用它從父類繼承下來的方法,操作父類中未被子類繼承卻被分配了內(nèi)存空間的私有成員變量。

classFather{

privateinthouse_1_area;

publicFather(inth1a){

house_1_area=h1a;

}

publicintget_house_1_area(){

returnhouse_1_area;

}

}

classSonextendsFather{

privateinthouse_2_area;

publicSon(inth1a,inth2a){

super(h1a);

house_2_area=h2a;

}

publicintget_house_2_area(){

returnhouse_2_area;

}

}

classGrandSonextendsSon{

privateinthouse_3_area;

publicGrandSon(inth1a,inth2a,inth3a){

super(h1a,h2a);

house_3_area=h3a;

}

publicintget_house_3_area(){

returnhouse_3_area;

}

}

publicclassExtendTest2{

publicstaticvoidmain(String[]args){

GrandSongs=newGrandSon(60,90,120);

System.out.println(“Theareaoffather‘shouseis”+gs.get_house_1_area());

System.out.println(“Theareaoffather’shouseis"+gs.get_house_2_area());

System.out.println("Theareaoffather'shouseis"+gs.get_house_3_area());

}

}

程序運行結(jié)果和例4-1的運行結(jié)果相同,也如圖4.1所示。

4.3成員變量隱藏與方法覆蓋

在一個子類中,如果存在與父類成員變量名字相同的成員變量,同名的父類成員變量會被隱藏,即使它們的類型不同,也即子類重新聲明定義了這個成員變量。一旦子類通過重新定義聲明隱藏了父類的同名成員變量,就不能在子類中直接訪問父類的這個同名成員變量。但是子類仍然可以通過從父類繼承的方法,操作那些被隱藏的父類成員變量。

【例4-3】在這個例子中,子類Son通過聲明一個double類型的成員變量house_area,隱藏了父類Father中的同名整型變量。盡管如此,在子類中仍然可以通過調(diào)用父類的公有方法print_father_house_area來訪問被隱藏的父類同名整型變量。

classFather{

inthouse_area=100;

publicvoidprint_father_house_area(){

System.out.println(“Theareaoffather‘shouseis”+house_area);

}

}程序運行結(jié)果如圖4.2所示。圖4.2例4-3的運行結(jié)果

如果在子類中定義一個方法,這個方法的名字、返回類型、參數(shù)個數(shù)和類型與從父類繼承下來的方法完全相同,則子類中的方法就覆蓋了父類中的方法。子類通過方法的覆蓋可以把父類的內(nèi)部狀態(tài)和對外的行為更改為自己的內(nèi)部狀態(tài)和對外的行為。值得注意的是,覆蓋父類的方法時,不可以降低被覆蓋方法的訪問權(quán)限。假設(shè)父類中某個方法的訪問權(quán)限是protected的,那么子類中的覆蓋方法可以定義為public和protected,但是不能定義為private或友好的。

【例4-4】在這個例子中,子類Son通過覆蓋父類的公有方法print_house_area,輸出了自己新定義的double類型成員變量houst_area的值。

classFather{

inthouse_area=100;

publicvoidprint_house_area(){

System.out.println(“Theareaoffather‘shouseis”+house_area);

}

}程序運行結(jié)果如圖4.3所示。圖4.3例4-4的運行結(jié)果如果希望類中的某些方法不能夠被子類中的方法所覆蓋,可以把它們聲明為final。所有類的公共父類Object類就是這樣做的,它的以下方法:

(1)?publicfinalClass<?>getClass();

(2)?publicfinalvoidnotify();

(3)?publicfinalvoidnotifyAll();

(4)?publicfinalvoidwait(longtimeout);

(5)?publicfinalvoidwait(longtimeout,intnanos);

(6)?publicfinalvoidwait();都被final關(guān)鍵字所修飾。這是因為這些方法都和Java虛擬機的核心實現(xiàn)有關(guān),不允許被它的子類進行修改。特別是后五個方法,它們都是和線程同步相關(guān)的方法。線程同步相關(guān)的實現(xiàn)和操作系統(tǒng)有很大的關(guān)系,在Java平臺中,這些方法是使用操作系統(tǒng)本地編程語言實現(xiàn)的,因此不能在Java運行時環(huán)境中進行重新定義。除了把類中的一些方法聲明為final,還可以聲明整個類為final。一個被final關(guān)鍵字修飾的類不能夠被繼承,也即不能夠有子類。有時候處于安全性的考慮,會將一些類聲明為final,比如Java.lang包中的String類。由于它涉及到字符串操作,對于Java編譯器和Java解釋器的正常運行有很重要的作用,不允許被更改,因此它被聲明為final。

4.4super關(guān)鍵字

在Java中,關(guān)鍵字super有兩種用法,其中一種是子類方法通過super調(diào)用父類中被子類隱藏的成員變量和覆蓋的成員方法。從上一節(jié)中我們已經(jīng)知道,在一個子類中,與父類成員變量名字相同的成員變量會使同名的父類成員變量被隱藏,如果在子類中定義一個方法,這個方法的名字、返回類型、參數(shù)個數(shù)和類型與從父類繼承下來的方法完全相同,則子類中的方法就會覆蓋了父類中的方法。但是父類中被隱藏的成員變量和被覆蓋的成員方法仍然可以通過super關(guān)鍵字來訪問。例如在例4-4的子類Son中,如果使用以下語句:

super.house_area=120;

super.print_house_area();

就可以分別調(diào)用父類Father中被子類Son隱藏的成員變量house_area和覆蓋的成員方法print_house_area()。

【例4-5】在這個例子中,子類Son使用super調(diào)用父類中被隱藏的成員變量和被覆蓋的成員方法。

classFather{

inthouse_area=100;

publicvoidprint_house_area(){

System.out.println(“Theareaoffather‘shouseis”+house_area);

}

}程序運行結(jié)果如圖4.4所示。圖4.4例4-5的運行結(jié)果另一種是子類的初始化方法中使用super調(diào)用父類的初始化方法。我們前面也提到了,子類不繼承父類的初始化方法。但是如果在子類的初始化方法中沒有顯式的調(diào)用父類的初始化方法,那么Java編譯器將會自動的在調(diào)用子類的初始化方法之前調(diào)用父類的無參初始化方法完成對父類中成員變量的初始化,而不管這些成員變量是否被子類所繼承。如果父類中沒有提供無參的初始化方法,編譯時就會出現(xiàn)錯誤。假如子類想顯式的調(diào)用父類中的某一個初始化方法,則必須在子類的初始化方法的第一條語句中使用super關(guān)鍵字調(diào)用父類的初始化方法。比如:

super();調(diào)用父類的無參初始化方法。

super(a,b);調(diào)用父類的帶有兩個參數(shù)的初始化方法,把實參a和b傳遞進去。

值得注意的是,無論子類的初始化方法顯式或隱式的調(diào)用父類的初始化方法,都會導(dǎo)致父類的某個初始化方法被執(zhí)行。而如果這個父類也有自己的父類的話,它的父類的初始化方法也會被執(zhí)行。因此在整一個繼承樹上,從葉子節(jié)點一直追溯到根節(jié)點,也即Object類,會有一系列的存在相互繼承關(guān)系的類的初始化方法被調(diào)用。舉一個例子,如果某一個類A一直追溯到Object類有20個直接或間接的祖先,那么初始化一個類A的對象實例就需要連續(xù)執(zhí)行20個類的初始化方法。當(dāng)繼承關(guān)系非常復(fù)雜的時候,對象實例的初始化將會消耗大量的系統(tǒng)資源,導(dǎo)致程序執(zhí)行效率的降低。

【例4-6】在這個例子中,子類Son在它的初始化方法中使用super調(diào)用了父類的初始化方法。

classFather{

inthouse_area;

publicFather(intha){

house_area=ha;

}

publicvoidprint_house_area(){

System.out.println(“Theareaoffather‘shouseis”+house_area);

}

}程序運行結(jié)果如圖4.5所示。圖4.5例4-6的運行結(jié)果4.5上?轉(zhuǎn)?型?對?象

在現(xiàn)實世界中,我們可以經(jīng)??吹筋惱^承和類抽象關(guān)系的實際例子。比如“人”有很多種職業(yè),比如“工人”、“農(nóng)民”、“軍人”和“警察”等。我們可以說“工人是人”,“農(nóng)民是人”,也即我們可以把這些不同職業(yè)的人的公共屬性抽象出來,封裝成一個更高級的類“人”。當(dāng)我們說工人也是人的時候,強調(diào)的是人類所共有的屬性和功能,而不再關(guān)心工人作為一種職業(yè)相對于農(nóng)民和軍人等其它職業(yè)所特有的屬性和功能。在面向?qū)ο蟮木幊谈拍钪?,假如我們有一個工人類Worker,它是Person類的子類。當(dāng)我們用Worker類創(chuàng)建一個對象,并把指向這個對象實體的引用保存在一個Person類的對象變量中的時候,如以下代碼所示:

Personp=newWorker();

我們就稱對象變量p為子類Worker的對象實體的上轉(zhuǎn)型對象變量。上轉(zhuǎn)型對象變量所引用的對象實體是由子類負(fù)責(zé)創(chuàng)建的。這個對象實體在創(chuàng)建時使用的類模板和上轉(zhuǎn)型對象變量的類模板不同,因此,上轉(zhuǎn)型對象變量在使用時,會失去原對象實體的一些屬性和功能。上轉(zhuǎn)型對象變量的使用具有如下特點:

(1)不能通過上轉(zhuǎn)型對象變量訪問子類對象實體中的成員變量和成員方法。這是因為上轉(zhuǎn)型對象變量通過父類模板去尋找對應(yīng)的成員變量名和成員方法名,而父類模板中并沒有包含子類模板中定義的成員變量和成員方法。

(2)子類對象實體中繼承的父類成員變量和成員方法,以及子類對象實體中重寫的父類成員方法,都可以通過上轉(zhuǎn)型對象變量訪問。需要注意的是,如果通過上轉(zhuǎn)型對象變量訪問子類對象實體重寫的父類成員方法,所執(zhí)行的代碼是子類中重寫的方法體。這是因為盡管上轉(zhuǎn)型對象通過父類模板去尋找對應(yīng)的成員方法名,但是通過這個方法名所執(zhí)行的代碼卻是子類對象實體中重寫過的方法體。

(3)可以通過上轉(zhuǎn)型對象變量訪問父類中被子類隱藏的成員變量。這也是因為上轉(zhuǎn)型對象變量通過父類模板去尋找對應(yīng)的成員變量,它所找到的正是父類中被子類隱藏的成員變量。

(4)可以通過強制類型轉(zhuǎn)換將一個上轉(zhuǎn)型對象變量轉(zhuǎn)換為一個子類對象變量。這樣新的子類對象變量又重新?lián)碛凶宇惖乃袑傩院凸δ堋?/p>

【例4-7】在這個例子中,我們借用了例4-6中的Son和Father類的代碼。在測試類的main方法中,我們初始化了一個Son類的對象實體,并把它的引用復(fù)制給Father類的上轉(zhuǎn)型對象變量fvar。通過fvar我們能訪問子類重寫的方法print_house_area以及父類中本來被覆蓋掉的成員變量house_area。再把fvar通過強制類型轉(zhuǎn)換變換為Son類型的對象變量svar后,我們能夠訪問子類重寫的方法print_house_area以及子類中把父類同名成員變量覆蓋掉的成員變量house_area。程序運行結(jié)果如圖4.6所示。圖4.6例4-7的運行結(jié)果如果一個類有很多子類,并且這些類都重寫了父類中的某個實例方法,把這些子類的對象實體的引用分別賦值給父類的一個對象變量。從上述可知,這個對象變量就是這些子類的對象實體的上轉(zhuǎn)型對象變量。通過這個上轉(zhuǎn)型對象變量訪問那個實例方法時,所調(diào)用的就是子類重寫后的實例方法體。這在面向?qū)ο缶幊谈拍钪惺且环N多態(tài)性的表現(xiàn)。與繼承有關(guān)的多態(tài)性指的是父類的某個實例方法被其子類重寫時,可以產(chǎn)生不同的功能行為,也即同一個操作被不同類型的對象實體調(diào)用時可能產(chǎn)生不同的行為。舉例來說,抽象的類模型“人”可能有一個方法“工作”,對于它的子類而言,“工人”的工作是在車間勞作,“農(nóng)民”的工作是在田間耕作,而“警察”的工作是在維護治安。

【例4-8】在這個例子中,父類Person有一個方法working,它的三個子類Worker、Peasant和Policeman分別重寫了它的working方法。在測試類的main方法中,創(chuàng)建了三個子類的實體并把它們分別復(fù)制給Person類的實例對象變量person。通過person分別調(diào)用working方法,我們可以看到不同的輸出。程序運行結(jié)果如圖4.7所示。圖4.7例4-8的運行結(jié)果4.6抽象類

抽象類就是被關(guān)鍵字abstract修飾的類。抽象類不能直接使用new操作符進行實例化,但是能夠使用一個抽象類的對象變量存放它的具體化子類實體的引用。抽象類中可能包含也可能不包含抽象方法。所謂抽象方法,就是abstract關(guān)鍵字修飾的方法。抽象方法只有聲明,沒有實現(xiàn),也即是說在抽象方法的聲明后沒有大括號括起來的方法體,后面直接跟分號。例如:

abstractvoidworking();定義了一個抽象方法working,它只有方法聲明,沒有方法體,后面直接跟著分號。如果一個類中包含有抽象方法,則該類本身必須被關(guān)鍵字abstract修飾。抽象類的子類需要提供抽象類中抽象方法的具體實現(xiàn),如果子類沒能提供父類中所有抽象方法的實現(xiàn),那么它也是一個抽象類,必須被關(guān)鍵字abstract修飾。

【例4-9】抽象類的例子。在這個例子中,父類Person是一個抽象類,它擁有一個抽象方法working。它的三個子類Worker、Peasant和Policeman分別具體化實現(xiàn)了它的working抽象方法。在這個例子中我們可以看到,抽象類的對象變量可以充當(dāng)其具體化的子類對象實體的上轉(zhuǎn)型對象變量。

4.7接口

Java不支持多重繼承,也即一個類只能有一個父類。單繼承性使得Java語言體系變得更為簡單,但是單繼承性也使得Java缺乏C++?中多重繼承具有的靈活性。為了解決這個問題,Java語言提供了一種新的語言特性,就是接口,接口提供了多重繼承的一種替代方式。在Java中,一個類只能繼承一個父類,但是它可以實現(xiàn)多重接口。因此,一個對象可以通過提供多重接口來獲得多重類型:它所對應(yīng)的類模板的類型,它通過單繼承獲得的繼承樹上的各個祖先的類型,以及它和它的各個祖先實現(xiàn)的所有接口的類型。所以,接口是Java中一個非常重要的語言特性。

Java中的接口是一種和抽象類非常類似的引用類型。接口使用關(guān)鍵字interface來聲明。接口中只能包含常量、方法聲明,而不能包含方法體。接口只能用于聲明變量,而不能實例化。接口只能被某個類實現(xiàn)或被其它接口所繼承。

定義接口的格式如下:

interface接口的名字{

……

}比如下面的例子定義了一個接口Workable,它包含有一個整型的常量WORKDAY_MAX和對外的的接口方法working()。注意working()方法只有方法聲明,沒有方法體。

publicinterfaceWorkable{

finalintWORKDAY_MAX=5;

voidworking();

}一個類通過使用關(guān)鍵字implements聲明自己實現(xiàn)一個或多個接口,如果一個類要實現(xiàn)多個接口,則用逗號隔開接口的名字。例如Worker類繼承了Person類,實現(xiàn)了兩個接口Workable和Playable。

publicclassWorkerextendsPersonimplements

Workable,Playable{

}需要注意的是,接口中的方法默認(rèn)都是public和abstract的,因此接口在聲明方法時可以省略掉方法前面的public和abst

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論