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

下載本文檔

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

文檔簡介

第6章多態(tài)性6.1多態(tài)性的概念6.2抽象類6.3接口6.4JAR文檔第6章多態(tài)性學(xué)習(xí)目標(biāo)

理解Java語言多態(tài)性的概念;

掌握抽象類的概念和定義方法;

理解接口和接口的實現(xiàn)技術(shù)。從字面意思來說,多態(tài)(polymorphism)就是指“多種形態(tài)”。那么,在Java中什么量可以有多種形態(tài)呢?前面我們介紹的方法重載中,同一個方法名根據(jù)需要在程序中可以定義多個不同的方法體,這就是Java語言中多種形態(tài)的方法。因此,簡單地說,多態(tài)就是指同名但形態(tài)(即功能)不同的方法。多態(tài)性提高了程序設(shè)計的抽象性和簡潔性,是面向?qū)ο蟪绦蛟O(shè)計的一個重要特征。本章介紹與實現(xiàn)多態(tài)有關(guān)的Java概念和技術(shù),主要內(nèi)容有多態(tài)的概念、抽象類與多態(tài)、接口與多態(tài)。6.1多態(tài)性的概念在第5章介紹類的層次結(jié)構(gòu)時我們說過,一個父類的引用變量可以指向一個子類的實例對象,而一個父類可能有多個子類。如我們說“狗是一種哺乳動物”,也可以說“馬是一種哺乳動物”,“貓是一種哺乳動物”,等等,這里的“哺乳動物”是父類(如用Mammal表示),而狗、馬、貓等是“哺乳動物”的子類。有如下的類定義:publicclassMammal{…}classDogextendsMammal{…}classHorseextendsMammal{…}classCatextendsMammal{…}當(dāng)我們說明了如下的一個“哺乳動物類”的引用animal時:Mammalanimal;

animal可能在程序運行的不同時刻指向了Mammal子類的不同對象。例如,在某個時刻可能指向的是一個狗類(Dog)的實例對象,而在另外一個時刻可能指向的是貓類(Cat)的實例對象或馬類(Horse)的實例對象。像animal這樣在不同時段內(nèi)可以指向不同類型對象的引用,就是多態(tài)性的引用。Java的多態(tài)性就是指方法的多態(tài)和引用的多態(tài)。下面介紹有關(guān)多態(tài)的基本知識。6.1.1多態(tài)的基本知識在Java語言中,多態(tài)性分為兩種類型。

1.由方法多態(tài)引發(fā)的編譯時多態(tài)性編譯時多態(tài)性是由方法重載實現(xiàn)的。前面介紹過,重載是指在同一個類(也可以是子類與父類)中,同一個方法名被定義多次,但所采用的形式參數(shù)列表不同(可能是參數(shù)的個數(shù)、類型或順序不同)。在重載方法的編譯階段,編譯器會根據(jù)所調(diào)用方法參數(shù)的不同,具體來確定要調(diào)用的方法。如下面的程序中定義了一個類A:classA{…A(){…}A(intx){…}A(intx,inty){…}…}類A中定義了三個重載的構(gòu)造方法,則在實例化一個對象時,會根據(jù)構(gòu)造方法中實參的類型與個數(shù)確定調(diào)用類A中的哪個構(gòu)造方法。如程序中有下面一條語句:Aa=newA(12);則編譯時,系統(tǒng)就可以確定該處要調(diào)用類A中有一個參數(shù)的構(gòu)造方法。

2.由引用多態(tài)引發(fā)的運行時多態(tài)性由于子類繼承了父類所有的屬性,因此子類對象可以作為父類對象使用。程序中凡是使用父類對象的地方,都可以用子類對象來代替,因此,如果說明了一個父類的引用,則這個引用就是多態(tài)性的引用。對于多態(tài)性的一個引用,調(diào)用方法的原則是:Java運行時,系統(tǒng)根據(jù)調(diào)用該方法的實例來決定調(diào)用哪個方法。對子類的一個實例,如果子類重寫了父類的方法,則運行時系統(tǒng)調(diào)用子類的方法;如果子類繼承了父類的方法(未重寫),則運行時系統(tǒng)調(diào)用父類的方法。編譯時多態(tài)性比較簡單,本章我們主要討論運行時多態(tài)性。6.1.2【案例6-1】吃水果

1.案例描述一天,小明的媽媽買了一籃子水果(Fruit),有香蕉(Banana)、蘋果(Apple)和椰子(Coconut)。小明在吃這些水果時,如果拿到了一個香蕉就要“剝了皮吃”,如果拿到了一個蘋果就要“削了皮吃”,如果拿到了一個椰子就要“鉆一個孔來喝”?,F(xiàn)要求設(shè)計一個程序,模擬小明選擇水果和吃水果的過程。

2.案例效果案例程序的執(zhí)行效果如圖6-1所示。圖6-1案例6-1的執(zhí)行效果

3.技術(shù)分析該問題域中,香蕉(Banana)、蘋果(Apple)和椰子(Coconut)是不同種類的水果(Fruit),它們都是水果里的一個子類,因此這里的父類是水果類(Fruit),子類是香蕉類(Banana)、蘋果類(Apple)和椰子類(Coconut)。水果都可以吃,所以水果類中要定義一個“吃”水果(eat)的方法。

4.程序解析下面是該案例的程序代碼:01//******************************************02//案例:6-103//程序名:EatFruit.java04//功能:模擬吃水果的程序05//******************************************0607importjava.util.Scanner;0809//水果類10classFruit{11 voideat(){}12}1314//蘋果類15classAppleextendsFruit{16 voideat(){17 System.out.println("蘋果,要削了皮吃!");18 }19}2021//香蕉類22classBananaextendsFruit{23 voideat(){24 System.out.println("香蕉,要剝了皮吃!");25 }26}2728//椰子類29classCoconutextendsFruit{30 voideat(){31 System.out.println("椰子,要鉆一個孔來喝!");32 }33}343536classEatFruit{ 37 38 //選擇所要吃的水果39 staticvoidchooseFruit(Fruit[]f){40 intfruitType;41 Scannersc=newScanner(System.in);42 43 System.out.println("");44 System.out.println(“請選擇你要吃的水果:\n1.蘋果

\t2.香蕉\t3.椰子");45 System.out.println("");46 47 for(inti=1;i<=f.length;i++){48 System.out.print("請選擇要吃的第"+i+"個水果:");49 50 //如果選擇的水果不正確,要重新選擇51 while(true){52 fruitType=sc.nextInt();53 if((fruitType==1)||(fruitType==2)||(fruitType==3))54 break;55 else System.out.print("請重新選擇要吃的第"+i+“個水果:");56 }57 58 //根據(jù)所選擇的水果,創(chuàng)建相應(yīng)的實例59 switch(fruitType){60 case1:f[i-1]=newApple();break;61 case2:f[i-1]=newBanana();break;62 case3:f[i-1]=newCoconut();break;63 }64 }65 }66 67 //開始吃水果68 staticvoidstartEating(Fruit[]f){69 System.out.println("");70 for(inti=1;i<=f.length;i++){71 System.out.print("第"+i+"個水果是");72 f[i-1].eat();73 }74 }75 76 77 publicstaticvoidmain(String[]args){78 Fruit[]f=newFruit[6];79 chooseFruit(f);80 startEating(f);81 }82}該程序中,第10~12行定義了一個水果類Fruit,在其后面定義的Apple類、Banana類和Coconut類都繼承了Fruit類,即這3個類都是Fruit類的子類,在每個子類中都重寫了父類中的eat方法。父類中的eat方法體為空,因為Friut類中只知道每一種水果都可以吃,但具體要吃什么水果是不知道的,所以吃什么水果只能在子類中根據(jù)具體情況重新定義。在本程序中,定義了一個吃水果的類EatFruit。該類的代碼顯得比較復(fù)雜些,其中39~65行定義了一個選擇所要吃水果的方法chooseFruit,通過該方法可以將所選擇要吃的水果存放在一個數(shù)組中。該數(shù)組的每個元素是一個對象,具體根據(jù)所選擇水果種類的不同可以是Apple類、Banana類或Coconut類的實例對象,這個過程是由程序59~63行的一個switch語句來實現(xiàn)的。在該switch語句中根據(jù)所選擇水果的不同,分別創(chuàng)建了Apple類、Banana類或Coconut類的對象。在EatFruit類中,最關(guān)鍵的是68~74行定義的吃水果的方法startEating,正是該方法根據(jù)所選擇水果的不同,調(diào)用了不同的吃水果的方法,完成了所謂多態(tài)性方法的調(diào)用。程序中70~73行是一個for循環(huán)語句,其循環(huán)體中的“f[i-1].eat()”語句,會根據(jù)數(shù)組元素f[i-1]中存放的引用對象是Apple類、Banana類或Coconut類的實例,來調(diào)用其相應(yīng)子類中定義的eat()方法。這就是本節(jié)要重點介紹的內(nèi)容。一個父類的引用,會根據(jù)當(dāng)前所指向?qū)ο蟮牟煌{(diào)用相應(yīng)子類中被重寫的方法,這就是所謂由引用多態(tài)引發(fā)的運行時多態(tài)性。運行時多態(tài)性在程序被編譯時,不能確定一個多態(tài)引用所要調(diào)用的方法,只有在程序運行時才能根據(jù)引用所指向?qū)ο蟮念悂泶_定被調(diào)用的方法。

EatFruit類中定義的方法其下標(biāo)都為“i-1”,是因為for循環(huán)中的循環(huán)變量i都從1開始,而一個數(shù)據(jù)的下標(biāo)是從0開始的。在main方法中,78行定義了一個元素類型是Fruit類的數(shù)組f,f中共有6個下標(biāo)元素。79行調(diào)用chooseFruit(f)方法將所選擇的水果存放在f數(shù)組中,80行調(diào)用startEating(f)方法開始吃水果。要注意的是,這兩個方法的參數(shù)都是數(shù)組f,在調(diào)用chooseFruit(f)方法前,f數(shù)組中沒有存放任何元素;當(dāng)該方法執(zhí)行完成后,將所選擇的水果存放在f數(shù)組中,這樣在調(diào)用startEating(f)方法吃水果時,f數(shù)組中已經(jīng)存放了所選擇的水果。當(dāng)數(shù)組中的元素類型為對象時,會令初學(xué)者比較困惑。下面對這個問題進(jìn)行說明。程序中78行的語句是“Fruit[]f=newFruit[6]”,該語句執(zhí)行后的情況如圖6-2所示。該語句中的“Fruit[]f”聲明只完成圖6-2(a)所示的內(nèi)容,即只創(chuàng)建了一個數(shù)組型引用變量f?!皀ewFruit[6]”完成圖6-2(b)所示的內(nèi)容,即為引用f在內(nèi)存中創(chuàng)建6個指向Fruit型的數(shù)組元素,每個數(shù)組元素是一個Fruit型的對象。程序中60~62行的語句為創(chuàng)建每個數(shù)組元素對象(即下標(biāo)變量)所指向的對象實例,如圖6-2(c)所示。EatFruit類中的方法為了在程序中不實例化對象就可以調(diào)用,所以都聲明成了靜態(tài)方法(static)。圖6-2數(shù)組的元素為對象時的創(chuàng)建過程6.1.3【相關(guān)知識】數(shù)據(jù)的輸入與格式化輸出在一個程序中,經(jīng)常要從鍵盤上給某些變量輸入數(shù)據(jù),輸入的數(shù)據(jù)經(jīng)過程序處理后,又要以某種格式進(jìn)行輸出。下面介紹Java語言中與數(shù)據(jù)輸入和輸出有關(guān)的知識。

1.輸入的數(shù)據(jù)案例6-1程序第39行定義的chooseFruit(Fruit[]f)方法中,在第41行使用了Scanner類。Scanner類是Java5.0在java.util包中新增加的一個類,該類用于輸入數(shù)據(jù),其數(shù)據(jù)來源可以是文件、字符串或鍵盤等。使用Scanner類從鍵盤上輸入數(shù)據(jù)的步驟是:

(1)創(chuàng)建一個該類的對象,并指定輸入源。如果要從鍵盤上輸入數(shù)據(jù),常用類似于如下的格式:Scannersc=newScanner(System.in);其中,System.in在Java語言中表示標(biāo)準(zhǔn)輸入設(shè)備(另外,經(jīng)常使用System.out表示標(biāo)準(zhǔn)輸出設(shè)備,一般指顯示器),其實就是鍵盤,表示要從鍵盤上輸入數(shù)據(jù)。

Scanner對象一般使用空格符(包括空格、Tab鍵和換行符)分隔輸入的內(nèi)容。

(2)使用Scanner類提供的方法從數(shù)據(jù)源取得數(shù)據(jù)。Scanner類中定義的nextXXX方法將輸入內(nèi)容中的數(shù)據(jù)取出并轉(zhuǎn)換為不同類型的值。常用的方法有:●?nextBoolean():將掃描到的內(nèi)容轉(zhuǎn)換為一個布爾值,并返回該值;●?nextByte():將掃描到的內(nèi)容轉(zhuǎn)換為一個byte類型的值,并返回該值;●?nextDouble():將掃描到的內(nèi)容轉(zhuǎn)換為一個double類型的值,并返回該值;●?nextFloat():將掃描到的內(nèi)容轉(zhuǎn)換為一個float類型的值,并返回該值;●?nextInt():將掃描到的內(nèi)容轉(zhuǎn)換為一個int類型的值,并返回該值;●?nextLong():將掃描到的內(nèi)容轉(zhuǎn)換為一個long類型的值,并返回該值;●?nextShort():將掃描到的內(nèi)容轉(zhuǎn)換為一個short類型的值,并返回該值;●?nextLine():讀取一行內(nèi)容,并以字符串的形式返回該值。例如,以下代碼可使用戶從鍵盤上(System.in)讀取一個整數(shù),并將讀到的數(shù)據(jù)保存在變量i中:Scannersc=newScanner(System.in);inti=sc.nextInt();

2.格式化輸出數(shù)據(jù)程序中要輸出的數(shù)據(jù)通常要按特定格式輸出,如計算出的商品總價要以貨幣格式輸出,產(chǎn)品的合格率要以百分?jǐn)?shù)格式輸出等。在Java語言的java.text包中,提供的DecimalFormat類可以將數(shù)據(jù)按要求的格式進(jìn)行輸出。下面舉例說明DecimalFormat類的用法。

(1)創(chuàng)建輸出格式類的對象,并指定輸出格式。使用DecimalFormat類格式化輸出數(shù)據(jù)時,先要創(chuàng)建一個DecimalFormat類的對象,并在構(gòu)造方法的參數(shù)中以字符串的形式說明輸出數(shù)據(jù)的格式(這個說明輸出數(shù)據(jù)格式的字符串也叫掩碼)。例如:

DecimalFormatdf1=newDecimalFormat("¥0.00");掩碼“¥0.00”中的¥表示輸出數(shù)據(jù)的最前面要加一個貨幣符號¥,“0.00”表示小數(shù)點的左邊至少要有一位數(shù)字,而小數(shù)點的右邊有且只能有兩位數(shù)字。如果小數(shù)點后面的數(shù)字多于兩位,則將小數(shù)點后的第3位數(shù)字四舍五入;如果小數(shù)點后面的數(shù)字少于兩位,則用0補(bǔ)齊。下面創(chuàng)建的格式化輸出對象df2將在輸出數(shù)據(jù)的前面加“產(chǎn)品的合格率為:”字符串,并將數(shù)據(jù)以百分?jǐn)?shù)的格式輸出:DecimalFormatdf2=newDecimalFormat("產(chǎn)品的合格率為:0.00%");下面創(chuàng)建的格式化輸出對象df3將在輸出數(shù)據(jù)的小數(shù)點后面保留3位小數(shù),“#”表示將為輸出的數(shù)據(jù)保留位置,但會取消所有的前導(dǎo)0,小數(shù)點前面的數(shù)據(jù)每3位用“,”分隔,并在小數(shù)點前面至少要有一個數(shù)字:DecimalFormatdf3=newDecimalFormat("#,##0.000");

(2)使用DecimalFormat類的format方法將數(shù)據(jù)按指定格式輸出。如要將345.3789按df1格式輸出,則可以寫成:

System.out.println(df1.format(345.3789));也可以將按df1格式輸出的數(shù)據(jù)賦給一個字符串變量:

Strings=df1.format(345.3789);如要將345.3789按df2格式輸出,則可以寫成:

System.out.println(df2.format(345.3789));下面是格式化輸出的一個示例程序:01importjava.text.DecimalFormat;//引入格式化輸出類02classDataFormat{03 publicstaticvoidmain(String[]args){04 DecimalFormatdf1=newDecimalFormat("¥0.00");05 DecimalFormatdf2=newDecimalFormat(“產(chǎn)品的合格率為:0.00%");06 DecimalFormatdf3=new

DecimalFormat("#,##0.000");07 Strings=df2.format(0.72341);08 System.out.println(df1.format(234.237234));09 System.out.println(df2.format(0.876767));10 System.out.println(df3.format(234523450.237234));11 System.out.println(s);12 }13}程序的執(zhí)行結(jié)果如圖6-3所示。圖6-3示例程序格式化輸出的結(jié)果6.2抽象類案例6-1中的Fruit類定義了一個名為eat的方法,水果可以吃,但不同的水果有不同的吃法,因此Fruit類中不能確定吃水果的具體方法,只能在Fruit類的子類中具體定義。Fruit類中定義eat方法的目的只是告訴Fruit類的子類,對父類的eat方法要根據(jù)具體情況進(jìn)行重寫。像這樣一個方法在定義時,不能確定其方法體的具體內(nèi)容,在Java語言中可以定義為抽象方法。如果一個類中有抽象方法,則這個類應(yīng)該定義為抽象類。本節(jié)介紹抽象方法與抽象類的概念,并說明抽象類與多態(tài)的關(guān)系。6.2.1抽象類的基本知識

1.抽象方法像Fruit類中的eat方法,其方法體為空,這樣的方法只是告訴編譯器在子類中要重寫該方法,這時可將該方法定義為抽象方法。在Java語言中,抽象方法要用abstract關(guān)鍵字來修飾,一個抽象方法沒有方法體,只有方法的聲明部分(即方法頭)。抽象方法的定義格式是:abstract返回類型抽象方法名([參數(shù)列表]);注意:抽象方法在參數(shù)列表的后面使用“;”代替了方法體。一個子類繼承了父類時,父類中定義的抽象方法要在子類中實現(xiàn)。子類中實現(xiàn)父類定義的抽象方法時,其方法名、返回值和參數(shù)類型要與父類中抽象方法的方法名、返回值和參數(shù)類型相一致。由于一個父類可能有多個子類,而每個子類對父類中的同一個抽象方法給出了不同的實現(xiàn)過程,這樣就會出現(xiàn)同一個名稱、同一種類型的返回值在不同的子類中實現(xiàn)的方法體不同的情況,這正是實現(xiàn)多態(tài)的基礎(chǔ)。注意1:由于抽象方法在子類中要重新定義,因此不能將其聲明為靜態(tài)方法,即不能用static修飾。注意2:一個類的構(gòu)造方法不能聲明成抽象方法。

2.抽象類在Java語言中,如果一個類中定義了抽象方法,則這個類一定要定義成抽象類。一個抽象類用abstract關(guān)鍵字修飾。定義抽象類的格式如下:abstractclass抽象類名{…}抽象類必須被繼承,抽象方法必須在子類中被重寫,除非這個子類也是一個抽象類。注意1:抽象類中可以包含非抽象的方法。注意2:一個抽象類不一定要包含抽象方法,即使沒有一個抽象方法的類,也可以聲明成抽象類。注意3:若類中包含了抽象方法,則該類必須定義為抽象類。注意4:抽象類不能用來實例化對象。在學(xué)習(xí)了抽象類以后,案例6-1中的Fruit類應(yīng)該定義為一個抽象類,定義方法如下:abstractclassFruit{ abstractvoideat();}要注意,方法eat應(yīng)以分號結(jié)束。將Fruit類改為抽象類以后,案例6-1的執(zhí)行結(jié)果不變。因為Fruit類為抽象類,不能用來實例化對象,所以程序中不能有類似于如下的語句:Fruitf=newFruit();在Fruit類的子類Apple、Banana和Coconut中,重寫的方法名eat之前不能再使用abstract修飾,否則會出現(xiàn)編譯錯誤。抽象類處在類層次結(jié)構(gòu)的較高層,其優(yōu)點是可以概括某類事物的共同屬性,在子類中只需簡單地描述其特有的內(nèi)容即可,這樣可以簡化程序的設(shè)計。

3.抽象類的繼承抽象類只能作為基類使用,因此必須被子類繼承。如果一個類繼承的是一個抽象類,則該類中要實現(xiàn)抽象類中的抽象方法。當(dāng)然,對于抽象類中的非抽象方法也可以在子類中進(jìn)行覆蓋。當(dāng)子類對父類中的抽象方法不進(jìn)行重寫時,這個子類就只能成為一個抽象類,這種情況比較少。下面是一個抽象類繼承的示例程序:01abstractclassA{02 abstractvoidf();03 A(){04 System.out.println("Aconstructor");05 }06}0708abstractclassBextendsA{09 B(){10 System.out.println("Bconstructor");11 }12}1314classCextendsB{15 C(){16 System.out.println("Cconstructor");17 }18 voidf(){19 System.out.println("methodf()ofclassC.");20 } 21}2223classTestAbstractClass{24 publicstaticvoidmain(String[]args){25 Aa=newC();26 a.f();27 }28}程序中定義了一個抽象類A,類A中定義了一個抽象方法f()和一個無參的構(gòu)造方法A()。類B繼承了類A,但是沒有實現(xiàn)基類A中的抽象方法f(),所以類B只能定義成一個抽象類。類C繼承了類B,并且實現(xiàn)了類B從父類A中繼承的抽象方法f(),類C定義成了一個非抽象的類,它可以用來實例化對象。25行聲明了一個A類的引用,但實例化成了其間接子類C的對象。26行調(diào)用了f()方法。該程序的輸出內(nèi)容如下:AconstructorBconstructorCconstructormethodf()ofclassC.從該例可以看出,在程序中可以聲明一個抽象類的引用,但一定要將其實例化成子類的一個對象。由于一個抽象類可能有多個子類,因此抽象類的引用可以指向不同子類的對象,這樣抽象類的這個引用就是一個多形態(tài)的引用。這個引用在調(diào)用子類中被重寫的方法時,會根據(jù)該引用當(dāng)前所指向?qū)ο箢愋偷牟煌{(diào)用不同類中的那個方法。這就是由抽象類引起的多態(tài)。注意:抽象類的構(gòu)造方法不能是抽象的。6.2.2【案例6-2】定義平面幾何形狀類

1.案例描述在一個數(shù)學(xué)軟件包的開發(fā)過程中,要求軟件包中有求常見平面圖形(如三角形、圓、矩形和正方形等)面積的功能,編寫程序?qū)崿F(xiàn)該功能。

2.案例效果該案例程序的執(zhí)行結(jié)果如圖6-4所示。圖6-4案例6-2的執(zhí)行結(jié)果

3.技術(shù)分析各種平面圖形都可以求出其面積,但對于不同的形狀,其求面積的方法不同,因此可以定義一個平面圖形形狀類Shape。Shape類中有一個求面積的抽象方法(area),之所以定義為抽象方法,是因為不同的平面圖形其求面積的方法不同。圓(Circle)、矩形(Rectangle)和三角形(Triangle)是不同的形狀,它們都是Shape的子類,而正方形(Square)是矩形(Rectangle)中的一種特殊類型,因此正方形是矩形的子類,如圖6-5所示。抽象類的類名在類圖中要用斜體表示,以區(qū)別于普通的類。圖6-5幾何形狀的類層次結(jié)構(gòu)

4.程序解析下面是案例6-2的程序代碼:001//******************************************002//案例:6-2003//程序名:TestShape.java004//功能:求各種形狀的面積005//******************************************006007importjava.text.*;008009//形狀抽象類010abstractclassShape{011 publicabstractdoublearea();012}013014//圓類015classCircleextendsShape{016 privatedoubler;017 018 publicCircle(doubler){019 this.r=r;020 }021 022 publicdoublearea(){023 return3.14*r*r;024 }025 026 publicStringtoString(){027 return"圓的半徑是:"+r;028 }029}030031//三角形類032classTriangleextendsShape{033 privatedoublea,b,c;034 035 Triangle(doublea,doubleb,doublec){036 this.a=a;037 this.b=b;038 this.c=c;039 }040 041 publicbooleanisTriangle(){042 return(a+b>c)&&(a+c>b)&&(b+c>a);043 }044 045 publicdoublearea(){046 doubles=0.5*(a+b+c);047 returnMath.sqrt(s*(s-a)*(s-a)*(s-c));048 }049 050 publicStringtoString(){051 return"三角形的三個邊長分別是:"+a+","+b+","+c;052 }053}054055//矩形類056classRectangleextendsShape{057 protecteddoublewidth,height;058 059 publicRectangle(doublewidth,doubleheight){060 this.width=width;061 this.height=height;062 }063 064 publicdoublearea(){065 returnwidth*height;066 }067 068 publicStringtoString(){069 return"矩形的寬是:"+width+",高是:"+height;070 }071}072073//正方形類074classSquareextendsRectangle{075 Square(doublewidth){076 super(width,width);077 }078 079 publicdoublearea(){080 returnsuper.area();081 }082 083 publicStringtoString(){084 return"正方形的邊長是:"+width;085 }086}087088//測試類089publicclassTestShape{090 publicstaticvoidmain(String[]args){091 Shape[]shapes=newShape[6];092 shapes[0]=newTriangle(23.2,45.4,4.7);093 shapes[1]=newCircle(23.4);094 shapes[2]=newRectangle(23.4,56.0);095 shapes[3]=newSquare(23.4);096 shapes[4]=newTriangle(23.2,35.4,23.7);097 shapes[5]=newCircle(2.0);098 099 DecimalFormatdf=newDecimalFormat("#0.00");100 101 for(inti=0;i<shapes.length;i++){102 if(shapes[i]instanceofTriangle){103 Trianglet=(Triangle)shapes[i];104 if(!t.isTriangle()){105 System.out.println("所給3個邊構(gòu)不成一個三角形!“

+shapes[i]);106 continue;107 }108 }109 System.out.println(shapes[i]+",面積是:"+

df.format(shapes[i].area()));110 }111 }112}在案例的010~012行定義了一個抽象類Shape,該類中定義了一個抽象方法area,表示任何一個平面幾何形狀都可以求其面積。015~071行定義了形狀類的3個子類,分別表示一種具體的幾何形狀,這些子類中都重寫了父類Shape中的抽象方法area,并在每個子類中重寫了Object類的toString方法。程序中074~086行定義的正方形類Square繼承了矩形類Rectangle。075行定義的構(gòu)造方法Square(doublewidth)調(diào)用了父類的構(gòu)造方法,但要注意,由于正方形只需知道邊長即可,因此正方形的構(gòu)造方法只有一個參數(shù)(即邊長)。076行的super(width,width)表示調(diào)用父類059行定義的構(gòu)造方法,但長和寬使用了相同的參數(shù)。080行的super.area()表示調(diào)用父類中064行求面積的方法。在測試類TestShape的091行定義了一個形狀類的數(shù)組,該數(shù)組有6個元素。由于Shape是一個抽象類,盡管在程序中可以聲明抽象類的引用(在這里就是每個下標(biāo)變量),但絕對不能創(chuàng)建一個抽象類的實例對象,即在程序中不能有類似于如下的語句:Shape[]shapes=newShape[6]; shapes[0]=newShape();shapes[1]=newShape();這是因為抽象類只能用于繼承,不能實例化對象。程序的099行創(chuàng)建了一個格式化輸出對象df,其中的參數(shù)#0.00表示小數(shù)點后有兩個數(shù)字,且小數(shù)點前至少有一位數(shù)。在輸出每種形狀的面積時,使用該格式輸出對象。程序的101~110行是一個循環(huán)語句,在循環(huán)體中計算并輸出了存放在數(shù)組shapes中的每個具體形狀的面積。?其中102行的shapes[i]instanceofTriangle用于測試shapes[i]中保存的對象是否為一個三角形(Triangle),如果是三角形,就要調(diào)用三角形類中041行定義的判斷三個邊能否構(gòu)成三角形的方法isTriangle,如果所給三個邊不能構(gòu)成一個三角形,則不能求面積,用106行的continue語句進(jìn)行下一次循環(huán)。該案例中,多態(tài)性就體現(xiàn)在109行的shapes[i].area()方法調(diào)用上,當(dāng)i取不同的值時,shapes[i]表示不同形狀的對象,因而系統(tǒng)會自動根據(jù)shapes[i]中所保存對象的所屬類,調(diào)用相應(yīng)類中求面積的方法,從而實現(xiàn)了多態(tài)性。6.2.3【相關(guān)知識】Object類的toString方法在第5章介紹過,Object類是Java語言中所有類的直接或間接父類,因此在Object類中定義的方法會被所有類繼承。Object類中定義的toString方法常常用來輸出類中的一些信息,如在案例6-2的每個子類中都重寫了Object類的toString方法,用來輸出與每個子類有關(guān)的形狀信息。

toString方法的最大優(yōu)點是:如果調(diào)用了含有對象參數(shù)的println方法,則系統(tǒng)會自動調(diào)用toString方法打印出相應(yīng)的信息。正因為如此,一個類中常常將Object類的toString方法進(jìn)行覆蓋,并在toString方法的方法體中以字符串的方式返回要輸出的信息,然后在println方法中直接以對象名為參數(shù)輸出返回的字符串信息,如案例6-2的105行和109行println中的shapes[i]。6.3接口在Java語言中,組成程序的基本構(gòu)件有兩種:一種是前面介紹的類;另一種是本節(jié)要介紹的接口。如果在一個源程序(即擴(kuò)展名為.java的文件)中定義了多個類,則程序在編譯后,每一個類對應(yīng)地要生成一個字節(jié)代碼文件(即擴(kuò)展名為.class的文件)。接口在Java語言中是一種與類相似的構(gòu)件,一個接口在編譯后也要生成一個字節(jié)代碼文件,但接口中只包含常量和抽象方法的定義。下面介紹有關(guān)接口的知識。6.3.1接口的基本知識接口(interface)將抽象類的概念更深入了一層。從形式上看,一個接口可以當(dāng)作是一個“純”抽象類,即接口中的方法只規(guī)定了方法名、參數(shù)列表以及返回類型,不定義方法主體,并且所有方法默認(rèn)為是公共的和抽象的(相當(dāng)于用public和abstract修飾)。接口中也可以包含基本數(shù)據(jù)類型的數(shù)據(jù)成員,但它們都默認(rèn)為用public、static和final修飾,即均為常量。

1.接口的定義接口不同于類,因此不能用class關(guān)鍵字標(biāo)識,定義一個接口要使用interface關(guān)鍵字。下面是定義一個接口的常用語法:[訪問權(quán)限]interface接口名稱{[public][static][final]類型名稱=常量值;[public][abstract]返回值類型方法名(參數(shù)列表);}接口的訪問權(quán)限(即interface前的修飾符)只有兩種:public和缺省狀態(tài)。如果沒有修飾符(即缺省狀態(tài)),則表示此接口的訪問只限于同一個包中的類。如果使用修飾符,則只能是public修飾符,表示此接口是公有的,可以被不同包中的類訪問。接口體中只包含方法聲明和常量定義。由于在默認(rèn)情況下接口中的方法是公共的和抽象的,因此接口中的數(shù)據(jù)都是常量。下面是一個簡單的接口定義實例://程序名:Printable.javapublicinterfacePrintable{intMAX_LINE_OF_PAGE=40;intMAX_LINE_SIZE=80;voidprint();}該接口中定義了兩個整型常量和一個方法。注意1:根據(jù)Java語言命名規(guī)則,基本數(shù)據(jù)類型為常量(即staticfinal修飾)時,常量名全部采用大寫字母(用下劃線分隔單個標(biāo)識符里的多個單詞)。注意2:?因為接口中的數(shù)據(jù)成員全是常量,所以在定義接口的時候必須給這些常量賦值,否則會產(chǎn)生編譯錯誤。使用下列命令編譯該接口:javacPrintable.java編譯后,用JDK自帶的工具軟件javap對生成的字節(jié)代碼文件進(jìn)行反編譯。該命令的用法為:javapPrintable反編譯后的結(jié)果為:publicinterfacePrintable{publicstaticfinalintMAX_LINE_OF_PAGE;publicstaticfinalintMAX_LINE_SIZE;publicabstractvoidprint();}由此可知,即使定義接口時在方法前面不加public和abstract,編譯系統(tǒng)也會自動加上這兩個修飾符,并在數(shù)據(jù)成員的前面自動加public、static和final三個修飾符。因此,在定義一個接口時,方法和數(shù)據(jù)成員一般不寫修飾符。接口與類一樣,可以通過繼承(extends)技術(shù)來產(chǎn)生新的接口,這與類的繼承類似,但一個子接口可以繼承多個父接口(而類的繼承只能是單繼承)。接口的繼承也使用關(guān)鍵詞extends,其格式如下:[訪問權(quán)限]interface子接口名稱extends父接口1,父接口2,…,父接口n{…}子接口將繼承父接口中定義的所有方法和常量。

2.使用接口的原因在實際軟件開發(fā)中,多個不相干的類如果存在相同的屬性和類似功能的方法,就可以將這些屬性和方法單獨組織起來,定義成一個單獨的程序模塊,這個模塊可以使用接口來定義。例如,在開發(fā)一個倉庫管理系統(tǒng)時,系統(tǒng)涉及到很多種物品類,如有汽車配件類、家用電氣類等,這些物品類都要具有打印出相關(guān)物品信息的功能,那么就可以定義一個如上的Printable接口,其中包含了打印輸出時每行的最大字符數(shù)MAX_LINE_SIZE(80個字符)和每頁最多打印的行數(shù)MAX_LINE_OF_PAGE(40行),還包含一個用于打印物品信息的print方法。由于不同的庫存物品要打印輸出的內(nèi)容差別很大,因此print方法中的操作內(nèi)容只能在具體類中定義。為什么不將Printable接口定義為一個抽象類呢?因為如果將一個接口定義成抽象類,那么繼承該抽象類的子類與抽象的父類之間應(yīng)該有“特殊與一般”的關(guān)系,如6.2節(jié)介紹的Rectangle子類繼承了抽象類Shape,它們之間就存在著“特殊與一般”的關(guān)系,即“矩形是一種形狀”。而接口與實現(xiàn)該接口的類之間沒有這種關(guān)系,如我們不能說“汽車配件是一種打印”,這不符合客觀事實和人們的思維邏輯。接口中只定義了人們關(guān)心的功能,并不考慮這些功能是如何實現(xiàn)的以及哪些類要實現(xiàn)這些功能。如我們定義了一個如下的接口:publicinterfaceCanFly{voidfly();}實現(xiàn)了這個接口的類表示可以飛行。鳥能飛,所以鳥類可以實現(xiàn)這個接口,飛機(jī)也能飛,所以飛機(jī)可以實現(xiàn)這個接口,其它可飛的東西也可以實現(xiàn)這個接口。在實際軟件開發(fā)中,假如你是一個項目經(jīng)理,需要管理多個開發(fā)人員,如果你希望開發(fā)的某些類要具有某種功能,最簡單的做法就是由你定義一個接口,然后指示開發(fā)人員在設(shè)計類時實現(xiàn)這個接口。因此,接口也定義了一種能力,實現(xiàn)了這個接口的類,可以說就具有了這個接口所規(guī)定的能力。Java系統(tǒng)類庫中標(biāo)準(zhǔn)接口的命名大都以able結(jié)尾(表示具有完成某功能的能力),比如Comparable、Cloneable、Runable等。在Comparable接口中就定義了一個名叫CompareTo的方法,所有實現(xiàn)了Comparable接口的類都可以使用該方法進(jìn)行兩個對象之間的比較??梢詫⒔涌诳偨Y(jié)為:接口定義了一個類對外提供服務(wù)的規(guī)范,一個類可以按照這種規(guī)范來實現(xiàn)規(guī)范中包含的功能,其它的類也可以按照這種規(guī)范來使用功能。

3.實現(xiàn)接口一個類可以實現(xiàn)一個或多個接口,實現(xiàn)接口的類要在implements關(guān)鍵詞后指出所實現(xiàn)接口的名稱。其語法如下:class類名implements接口名1,接口名2…{

類體}實現(xiàn)了接口的類,一般要重寫接口中的所有抽象方法,且方法名前要加public。下面是一個示例程序:01interfaceCanSwim{02voidswimming();03}0405interfaceCanRun{06voidrunning();07}0809classTurtleimplementsCanSwim,CanRun{10publicvoidswimming(){11System.out.println("Turtleisswimming.");12}1314publicvoidrunning(){15System.out.println("Turtleisrunning.");16}17}1819classFishimplementsCanSwim{20publicvoidswimming(){21System.out.println("Fishisswimming.");22}23}2425classTestInterface{26publicstaticvoidmain(String[]args){27 Turtlet=newTurtle();28 t.swimming();29 t.running();30 Fishf=newFish();31 f.swimming();32}33}該示例程序中定義了CanSwim和CanRun兩個接口,實現(xiàn)了CanSwim接口的類表示能游泳,實現(xiàn)了CanRun接口的類表示能跑,龜(Turtle)既能游泳又能跑,所以實現(xiàn)了CanSwim和CanRun兩個接口。一般的魚(Fish)只會游泳,所以只實現(xiàn)了CanSwim接口。在Fish類與Turtle類中,對所實現(xiàn)接口中聲明的方法進(jìn)行了具體的定義,并且方法一定要用public修飾,否則會出現(xiàn)編譯錯誤。這是因為Java語言中規(guī)定,在類中實現(xiàn)接口中定義的方法時,不能比接口中定義的方法有更低的訪問權(quán)限。接口中定義的方法都是公共的,所以這些方法在實現(xiàn)接口的類中定義時,只能定義成公共的。注意:一個類只能有一個父類,但可以實現(xiàn)多個接口。如定義了一個動物類:classAnimal{…}則龜(Turtle)類可以定義為:classTurtleextendsAnimalimplementsCanSwim,CanRun{…}即Turtle類繼承了Animal類,實現(xiàn)了CanSwim和CanRun兩個接口。注意:如果一個類中沒有實現(xiàn)接口中聲明的所有方法,則這個類只能定義為一個抽象類。為了簡單起見,Java語言不支持多重繼承,即一個類不能有多個父類。如果在程序中確實要實現(xiàn)多重繼承的機(jī)制,可以借助于接口來實現(xiàn),因為一個類可以實現(xiàn)多個接口,如上例中的Turtle類。初學(xué)者在程序中使用接口時,應(yīng)注意以下問題:●避免接口中所有的方法都用publicabstract修飾。●避免接口中所有的數(shù)據(jù)成員都用publicstaticfinal修飾(即為常量)。●接口中的數(shù)據(jù)成員在定義時必須有初值?!裨陬愔袑崿F(xiàn)接口中定義的方法時,必須用public修飾?!窠涌诤统橄箢愐粯?,都不能用來創(chuàng)建實例對象。由于接口中定義的數(shù)據(jù)成員都是靜態(tài)的和公共的常量,而靜態(tài)數(shù)據(jù)成員屬于類成員,因此在實現(xiàn)了接口的類中,可以直接以“接口名.常量名”的方式引用接口中定義的數(shù)據(jù)成員。6.3.2【案例6-3】可以飛行的類

1.案例描述定義具有可飛行特性的類。該案例的目的是為了說明接口在程序中的應(yīng)用和接口是如何實現(xiàn)多態(tài)性的。

2.案例效果案例程序的執(zhí)行效果如圖6-6所示。從圖中可以看出,鳥可以在空中飛行,飛機(jī)也可以在空中飛行。圖6-6案例6-3的執(zhí)行效果

3.技術(shù)分析飛行并不是某類所專有的特性,鳥可以飛行,飛機(jī)也可以飛行,而鳥和飛機(jī)是兩個互不相干的類,只不過它們都具有可飛行的特性。因此,可以定義一個具有“可飛行(Flyable)”功能的接口,在定義鳥類和飛機(jī)類時分別去實現(xiàn)“可飛行”這個接口。

4.程序解析案例程序如下:01//******************************************02//案例:6-303//程序名:TestFlyable.java04//功能:定義可以飛行的類05//******************************************0607interfaceFlyable{08 publicvoidfly();09}1011classBirdimplementsFlyable{12 publicvoidfly(){13 System.out.println("birdisflyingintheair.");14 }15}1617classPlaneimplementsFlyable{18 publicvoidfly(){19 System.out.println("planeisflyingintheair.");20 }21}2223classTestFlyable{24 staticvoidflying(Flyablef){25 f.fly();26 }27 28 publicstaticvoidmain(String[]args){29 Birdb=newBird();30 Planep=newPlane();31 flying(b);32 flying(p);33 }34}程序的07~09行定義了一個表示可飛行的接口Flyable,在可以飛行的接口中聲明了一個表示飛行的方法fly(),11~15行定義的Bird類實現(xiàn)了Flyable接口,17~21行定義的Plane類也實現(xiàn)了Flyable接口。在Bird類和Plane類中,都提供了對Flyable接口中fly()方法的具體實現(xiàn)。程序的23~34行定義了一個測試類TestFlyable,該類用于對可飛行的類進(jìn)行測試。在TestFlyable類中,定義了一個靜態(tài)的flying方法,該方法的參數(shù)是一個接口,調(diào)用該方法時,實參應(yīng)為實現(xiàn)了Flyable接口的實例對象。由于不同的類可以實現(xiàn)相同的接口,因此flying方法的參數(shù)可以是不同類的對象。但由于這些不同類的對象都實現(xiàn)了相同的接口,因此它們都可以完成接口中定義的功能。故我們可以得出這樣的結(jié)論:當(dāng)用接口變量調(diào)用接口中聲明的方法時,就通知相應(yīng)實現(xiàn)了接口的類的對象調(diào)用被類中所實現(xiàn)的那個方法。接口正是利用這個特點來實現(xiàn)多態(tài)性的。所以該程序中,在調(diào)用24行定義的flying方法時,如果實參是一個鳥類的對象,則25行就表示調(diào)用鳥類中所實現(xiàn)的fly()方法,即表示鳥在空中飛翔;如果實參是一個飛機(jī)類的對象,則25行就表示調(diào)用飛機(jī)類中所實現(xiàn)的fly()方法,即表示飛機(jī)在空中飛翔。注意:與抽象類相似,在方法的參數(shù)列表中和變量的聲明中可用接口作數(shù)據(jù)類型,但這種類型的變量必須指向一個實現(xiàn)了該接口的類的實例對象。6.3.3【相關(guān)知識】抽象類與接口的比較抽象類和接口的有些特性是相似的,如:●抽象類和接口都不能用來實例化對象?!窨梢月暶鞒橄箢惡徒涌诘淖兞浚珜Τ橄箢悂碚f,要用抽象類的非抽象子類來實例化該變量;對接口來說,要用實現(xiàn)了該接口的非抽象子類來實例化該變量?!褚粋€子類如果沒有實現(xiàn)抽象類中聲明的所有抽象方法,那么該子類也是一個抽象類;一個類如果沒有實現(xiàn)接口中聲明的所有方法,那么該類也是一個抽象類?!癯橄箢惡徒涌诙伎梢詫崿F(xiàn)程序的多態(tài)性。盡管抽象類和接口有些相似的特性,但它們在本質(zhì)上是有很大區(qū)別的:●抽象類在Java語言中體現(xiàn)的是一個“父與子”的關(guān)系,即抽象類與子類之間必須存在“子類是父類中的一種”的關(guān)系,如抽象類“水果”與子類“蘋果”之間就存在“蘋果是一種水果”的關(guān)系。而接口與接口的實現(xiàn)者之間不必有“父與子”的關(guān)系,接口的實現(xiàn)者只是具有接口中定義的行為而已。●抽象類中可以定義非抽象的方法,而接口中的所有方法都是抽象的?!窠涌谥械臄?shù)據(jù)成員只能是常量?!裨诔橄箢愔性黾右粋€方法并賦予其默認(rèn)的行為(即增加一個非抽象的方法)時,并不一定要修改子類,但如果接口被修改了,即增加或去掉了某個功能,則所有實現(xiàn)了該接口的類一定要重新修改。下面舉一個例子加以說明。比如說在一個簡單的商品管理系統(tǒng)中,可以定義一個抽象的商品類:abstractclassGoods{protecteddoublecost;abstractpublicvoidsetCost(doublecost);abstractpublicdoublegetCost();}每種商品都有價格cost,都可以設(shè)置(setCost)與取得(getCost)商品的價格。有些商品有過期日期(如食品類商品),有些商品沒有過期日期(如文具等商品),對于有過期日期的商品,希望能在過期前的一定時間內(nèi)進(jìn)行過期通知。顯然,對于每種商品來說,并不是都具有過期這種行為,因此,可以將過期行為定義為一個接口:publicinterfaceExpiration{voidsetExpirationDate(Datedate);voidexpire();}其中,setExpirationDate方法用于設(shè)置過期日期,expire方法用于過期日期的通知。那么,對服裝類商品可以定義如下:classClothesextendsGoods{…}而將食品類可以定義為:classFoodextendsGoodsimplementsExpiration{…}6.4JAR文檔在安裝Java后,會在安裝目錄的lib文件夾中建立一些擴(kuò)展名為?.jar的文件。jar文件是Java中非常有用的一種文件格式,它的一種壓縮文件格式類似于Windows中常見的ZIP文件。用Java語言開發(fā)的應(yīng)用程序可以使用jar.exe工具(在JDK安裝目錄的bin文件夾中)壓縮后進(jìn)行發(fā)布;另外,為使用方便起見,用戶常用的一些類也可以壓縮成jar文件。6.4.1創(chuàng)建可執(zhí)行的JAR文件使用jar.exe工具可以將應(yīng)用程序及其涉及到的類壓縮成一個jar文件,然后可以使用Java程序的解釋器java.exe來執(zhí)行這個壓縮文件。當(dāng)然,在執(zhí)行jar文件時需要使用-jar參數(shù)。下面舉例說明創(chuàng)建一個可執(zhí)行jar文件的步驟:

(1)準(zhǔn)備要壓縮的字節(jié)代碼文件。如在D:\java目錄中定義了如下的A類(文件名為A.java):publicclassA{ publ

溫馨提示

  • 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)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論