?
類(lèi)與繼承
?
?
?
??? 類(lèi)具有繼承性。子類(lèi)對(duì)父類(lèi)的繼承關(guān)系體現(xiàn)了現(xiàn)實(shí)世界中特殊和一般的關(guān)系。類(lèi)的繼承性大大簡(jiǎn)化了程序設(shè)計(jì)的復(fù)雜性。和類(lèi)的繼承性相聯(lián)系的對(duì)象的動(dòng)態(tài)綁定使對(duì)象的方法具有多態(tài)性。抽象類(lèi)和最終類(lèi)是兩種特殊的類(lèi)。接口和抽象類(lèi)非常類(lèi)似,Java語(yǔ)言只支持單繼承,但接口使Java語(yǔ)言實(shí)際上實(shí)現(xiàn)了多繼承。
?
?
1、面向?qū)ο蟮幕靖拍睿豪^承
?
??? 繼承是面向?qū)ο蟪绦蛟O(shè)計(jì)的又一個(gè)重要特性。繼承體現(xiàn)了類(lèi)與類(lèi)之間的一種特殊關(guān)系,即一般與特殊的關(guān)系。繼承就是一個(gè)新的類(lèi)擁有全部被繼承類(lèi)的成員變量和方法。繼承機(jī)制使得新類(lèi)不僅有自己特有的成員變量和方法,而且有被繼承類(lèi)的全部成員變量和方法。通過(guò)繼承,可以從已有類(lèi)模塊產(chǎn)生新的類(lèi)模塊,從而使兩個(gè)類(lèi)模塊之間發(fā)生聯(lián)系。通過(guò)繼承產(chǎn)生的新的類(lèi)模塊不僅重用了被繼承類(lèi)的模塊資源,而且使兩個(gè)類(lèi)模塊之間的聯(lián)系方式和人類(lèi)認(rèn)識(shí)客觀(guān)事物的方式一致。
??? 面向?qū)ο蟪绦蛟O(shè)計(jì)的繼承特性使得大型應(yīng)用程序的維護(hù)和設(shè)計(jì)變得更加簡(jiǎn)單。一方面,大型應(yīng)用程序設(shè)計(jì)完成并交付使用后,經(jīng)常面臨用戶(hù)的需求發(fā)生變化,程序功能需要擴(kuò)充等問(wèn)題。這時(shí)程序的修改需要非常謹(jǐn)慎,因?yàn)槟硞€(gè)局部的修改可能會(huì)影響其他部分,而一個(gè)正在使用中的系統(tǒng)要進(jìn)行全面的測(cè)試,則既費(fèi)時(shí)間又有很多實(shí)際的困難。另一方面,一個(gè)新的應(yīng)用系統(tǒng)程序設(shè)計(jì)問(wèn)題,在許多方面會(huì)和以前設(shè)計(jì)過(guò)的某個(gè)或某些系統(tǒng)的模塊非常類(lèi)似,怎樣加快大型應(yīng)用程序的開(kāi)發(fā)速度,重用這些已經(jīng)開(kāi)發(fā)成功的程序模塊,一直是軟件設(shè)計(jì)中非常希望有效解決的問(wèn)題。
??? 傳統(tǒng)的軟件設(shè)計(jì)解決上述兩類(lèi)問(wèn)題的方法主要有兩種:
??? 對(duì)于程序功能擴(kuò)充問(wèn)題,通常是直接對(duì)原代碼進(jìn)行改動(dòng)。這種方法雖然可行,但有可能對(duì)正在使用的其他模塊產(chǎn)生影響,通常靠測(cè)試的方法消除這種影響。但是,要對(duì)一個(gè)正在使用的系統(tǒng)進(jìn)行全面測(cè)試,既非常困難,代價(jià)又很大。
??? 對(duì)于模塊重用問(wèn)題,通常是對(duì)原模塊進(jìn)行復(fù)制。對(duì)復(fù)制的模塊再根據(jù)需要進(jìn)行改動(dòng),以支持新的功能。這種方法雖然可行,但仍然需要設(shè)計(jì)人員做很多工作,而且需要重新測(cè)試。
??? 面向?qū)ο蟪绦蛟O(shè)計(jì)的繼承機(jī)制可以很好地解決上述兩類(lèi)問(wèn)題。面向?qū)ο蟪绦蛟O(shè)計(jì)的繼承機(jī)制提供了一種重復(fù)利用原有程序模塊資源的途徑。通過(guò)新類(lèi)對(duì)原有類(lèi)的繼承,既可以擴(kuò)充舊的程序模塊功能以適應(yīng)新的用戶(hù)需求,也可以滿(mǎn)足新的應(yīng)用系統(tǒng)的功能要求。從而既可以大大方便原有系統(tǒng)的功能擴(kuò)充,也可以大大加快新系統(tǒng)的開(kāi)發(fā)速度。另外,用這種軟件設(shè)計(jì)方法設(shè)計(jì)的新系統(tǒng)較用傳統(tǒng)的軟件方法設(shè)計(jì)的新系統(tǒng)需要進(jìn)行的測(cè)試工作少很多。
?
?
2、繼承
1、子類(lèi)和父類(lèi)
?
??? 利用面向?qū)ο蟪绦蛟O(shè)計(jì)的繼承機(jī)制,我們可以首先創(chuàng)建一個(gè)包括其他許多類(lèi)共有的成員變量和方法的一般類(lèi),然后再通過(guò)繼承創(chuàng)建一個(gè)新類(lèi)。由于繼承,這些新類(lèi)已經(jīng)具有了一般類(lèi)的成員變量和方法,此時(shí)只需再設(shè)計(jì)各個(gè)不同類(lèi)特有的成員變量和方法。由繼承而得到的新類(lèi)稱(chēng)為子類(lèi),被繼承的類(lèi)稱(chēng)為父類(lèi)或超類(lèi)。子類(lèi)直接的上層父類(lèi)稱(chēng)作直接父類(lèi)。Java不支持多繼承,即一個(gè)子類(lèi)只能有一個(gè)直接父類(lèi)。
??? 例如,設(shè)父類(lèi)super已經(jīng)定義,當(dāng)類(lèi)sub1繼承類(lèi)super時(shí),就表明類(lèi)sub1是類(lèi)super的子類(lèi),或者說(shuō)類(lèi)super是類(lèi)sub1的父類(lèi)。子類(lèi)sub1由兩部分組成:繼承部分和增加部分。繼承部分是從父類(lèi)super繼承過(guò)來(lái)的,增加部分是子類(lèi)sub1新增加的。這樣,子類(lèi)繼承了父類(lèi)的成員變量和方法,從而可以共享已設(shè)計(jì)完成的軟件模塊。不僅如此,父類(lèi)super還可以作為多個(gè)子類(lèi)的父類(lèi),如子類(lèi)sub2也是父類(lèi)super的子類(lèi)。由于子類(lèi)sub1和子類(lèi)sub2有相同的父類(lèi),所以他們既有許多相同的性能,也有一些不同的功能。父類(lèi)和子類(lèi)之間的繼承關(guān)系如下圖所示:
?
???
??? 從上圖可知,具有繼承關(guān)系的若干個(gè)類(lèi)組成一棵類(lèi)樹(shù)。由于Java中所有的類(lèi)都是從Object類(lèi)繼承(或稱(chēng)派生)來(lái)的,所以,Java中所有的類(lèi)構(gòu)成一棵類(lèi)樹(shù)。
??? 注意:在圖所示的有三層繼承關(guān)系的類(lèi)中,最下層的Sub11和Sub12子子類(lèi),不僅繼承了直接父類(lèi)Sub1的成員變量和方法,而且繼承了間接父類(lèi)Super的成員變量和方法。如果沒(méi)有繼承機(jī)制,則一個(gè)軟件系統(tǒng)中的各個(gè)類(lèi)是各自封閉的、相互無(wú)關(guān)的。當(dāng)多個(gè)類(lèi)需要實(shí)現(xiàn)相似的功能時(shí),勢(shì)必會(huì)造成成員變量和方法的大量重復(fù)。而有了繼承機(jī)制,多個(gè)類(lèi)就可相互關(guān)聯(lián),新類(lèi)就可以從已有的類(lèi)中通過(guò)繼承產(chǎn)生。
??? 繼承有兩種基本形式:多繼承和單繼承。多繼承是指一個(gè)子類(lèi)可以繼承自多個(gè)直接父類(lèi)。單繼承是指一個(gè)子類(lèi)只可以繼承自一個(gè)直接父類(lèi)。Java語(yǔ)言只允許單繼承,不允許多繼承。
?
2、創(chuàng)建子類(lèi)
?
??? Java中的類(lèi)都是Object類(lèi)的子類(lèi)(當(dāng)然,很多類(lèi)是Object類(lèi)的間接子類(lèi))。Object類(lèi)定義了所有對(duì)象都必須具有的基本成員變量和方法。Java中的每個(gè)類(lèi)都從Object類(lèi)繼承了成員變量和方法,因而Java中的所有對(duì)象都具有Object類(lèi)的成員變量和方法。
??? 由于Java中的所有類(lèi)都是Object的直接子類(lèi)或間接子類(lèi),所以Java中的所有類(lèi)構(gòu)成一棵類(lèi)的層次樹(shù)結(jié)構(gòu)。
??? 定義類(lèi)有兩種基本方法:不指明父類(lèi)和顯式地指明父類(lèi)。Java語(yǔ)言規(guī)定,若定義類(lèi)時(shí)不指明父類(lèi),則其父類(lèi)是Object類(lèi)。本節(jié)介紹顯式的指明父類(lèi)的類(lèi)定義方法。
??? 顯式的指明一個(gè)類(lèi)的父類(lèi)的方法是,在類(lèi)定義時(shí)使用關(guān)鍵字extends,并隨后給出父類(lèi)名。類(lèi)定義語(yǔ)句格式為:
??? [<修飾符>]?class?<子類(lèi)名> extends?<父類(lèi)名>
??? 例如: class? Sub1? extends? Super
??? 就定義類(lèi)Sub1繼承自類(lèi)Super。此時(shí)我們說(shuō)類(lèi)Sub1是類(lèi)Super的子類(lèi),或者說(shuō)類(lèi)Super是類(lèi)Sub1的直接父類(lèi),直接父類(lèi)通常簡(jiǎn)稱(chēng)為父類(lèi)。
?
1.子類(lèi)繼承父類(lèi)的成員變量
???
子類(lèi)繼承了父類(lèi)中的成員變量。具體的繼承原則是:
??? (1)能夠繼承父類(lèi)中那些聲明為public和protected的成員變量。
??? (2)不能繼承父類(lèi)中那些聲明為private和默認(rèn)的成員變量。
??? (3)如果子類(lèi)聲明一個(gè)與父類(lèi)成員變量同名的成員變量,則不能繼承父類(lèi)的同名成員變量。此時(shí)稱(chēng)子類(lèi)的成員變量隱藏了父類(lèi)中的同名成員變量。
??? 因此,如果父類(lèi)中存在不允許其子類(lèi)訪(fǎng)問(wèn)的成員變量,那么這些成員變量必須以private修飾符聲明該成員變量;如果父類(lèi)中存在只允許其子類(lèi)訪(fǎng)問(wèn)、不允許其他類(lèi)訪(fǎng)問(wèn)的成員變量,那么這些成員變量必須以protected修飾符聲明該成員變量。
?
2.子類(lèi)繼承父類(lèi)的方法
??? 子類(lèi)繼承父類(lèi)方法的規(guī)則類(lèi)似于子類(lèi)繼承父類(lèi)成員變量的規(guī)則。具體的繼承原規(guī)是:
??? (1)能夠繼承父類(lèi)中那些聲明為public和protected的方法。
??? (2)不能繼承父類(lèi)中那些聲明為private和默認(rèn)的方法。
??? (3)如果子類(lèi)方法與父類(lèi)方法同名,則不能繼承。此時(shí)稱(chēng)子類(lèi)方法重寫(xiě)了父類(lèi)中的同名方法。
??? (4)不能繼承父類(lèi)的構(gòu)造方法。
??? 注意:和子類(lèi)繼承父類(lèi)成員變量的繼承原則不同的是,子類(lèi)不能繼承父類(lèi)的構(gòu)造方法。
?
3.this引用和super引用
????
(1)this引用。
???
Java中,每個(gè)對(duì)象都具有對(duì)其自身引用的訪(fǎng)問(wèn)權(quán),這稱(chēng)為this引用。訪(fǎng)問(wèn)本類(lèi)的成員變量和方法的語(yǔ)句格式為:
??? this.<成員變量名>
??? this.<方法名>
??? 例如:下面定義的類(lèi)X中有成員變量k,而在方法D中也用k作參數(shù),這樣兩個(gè)不同含義的變量k就有可能產(chǎn)生混淆,此時(shí)必須用this.k指代對(duì)象的成員變量k。
class X
{
??? int k;
??? void D(int k)
??? {
??????? this.k = 2*k;??? //this.k指成員變量k,k指參數(shù)k
??? }
}
????
(2)super引用。
??? 使用關(guān)鍵字super可以引用被子類(lèi)隱藏的父類(lèi)的成員變量或方法,這稱(chēng)為super引用。super引用的語(yǔ)句格式為:
??? super.<成員變量名>
??? super.<方法名>
??? super引用經(jīng)常用在子類(lèi)的構(gòu)造方法中。前面說(shuō)過(guò),子類(lèi)不能繼承父類(lèi)的構(gòu)造方法,但有時(shí)子類(lèi)的構(gòu)造方法和父類(lèi)的構(gòu)造方法相同時(shí),或子類(lèi)的構(gòu)造方法只需在父類(lèi)構(gòu)造方法的基礎(chǔ)上做某些補(bǔ)充時(shí),子類(lèi)構(gòu)造方法中需要調(diào)用父類(lèi)的構(gòu)造方法時(shí),此時(shí)的語(yǔ)句格式為:
??? super(<參數(shù)列表>)
??? 其中,<參數(shù)列表>是調(diào)用父類(lèi)構(gòu)造方法所需的參數(shù)。
?
4.成員變量和方法的隱藏與覆蓋
??? 子類(lèi)除了可以繼承父類(lèi)中的成員變量和方法外,還可以增加自已特有的成員變量和方法。當(dāng)父類(lèi)的某個(gè)成員變量不適合子類(lèi)時(shí),子類(lèi)可以重新定義該成員變量。前面說(shuō)過(guò),此種情況下,子類(lèi)隱藏了父類(lèi)的成員變量(程序設(shè)計(jì)中這種情況很少,一般也不提倡);當(dāng)父類(lèi)的某個(gè)方法不適合子類(lèi)時(shí),子類(lèi)可以重新定義它,這稱(chēng)為子類(lèi)對(duì)父類(lèi)方法的覆蓋(overriding)。
??? 子類(lèi)對(duì)父類(lèi)方法的覆蓋是面向?qū)ο蟪绦蛟O(shè)計(jì)中經(jīng)常使用的設(shè)計(jì)方法。在軟件功能擴(kuò)充和軟件重用中,可以通過(guò)設(shè)計(jì)新的子類(lèi),以及通過(guò)子類(lèi)方法對(duì)父類(lèi)方法的覆蓋,可以方便和快速地實(shí)現(xiàn)軟件功能的擴(kuò)充和軟件的重用。
??? 注意:方法的重寫(xiě)(overloading)和方法的覆蓋(overriding)是兩個(gè)不同的概念,在軟件設(shè)計(jì)中實(shí)現(xiàn)的功能也不同。
?
5.舉例
【例4.1】繼承舉例。
要求;設(shè)計(jì)一個(gè)Shape(形狀)類(lèi),再設(shè)計(jì)Shape類(lèi)的兩個(gè)子類(lèi),一個(gè)是Ellipse(橢圓)類(lèi),另一個(gè)是Rectangle(矩形)類(lèi)。每個(gè)類(lèi)都包括若干成員變量和方法,但每個(gè)類(lèi)都有一個(gè)draw()方法(畫(huà)圖方法),draw()方法中用輸出字符串表示畫(huà)圖。
程序設(shè)計(jì)如下:
class Shape??????????? //定義父類(lèi)Shape
{
??protected int lineSize;???????? //線(xiàn)寬
?
??public Shape()??????//構(gòu)造方法1
??{
??? lineSize = 1;
??}
?
? public Shape(int ls)?//構(gòu)造方法2
??{
??? lineSize = ls;
??}
?
? public void setLineSize(int ls)?//設(shè)置線(xiàn)寬
??{
??? lineSize = ls;
??}
?
? public int getLineSize()??????? //獲得線(xiàn)寬
??{
??? return lineSize;
??}
?
? public void draw()????????????? //畫(huà)圖
??{
??? System.out.println("Draw a Shape");
??}
}
?
class Ellipse extends Shape????//定義子類(lèi)Ellipse
{
? private int centerX;????//圓心X坐標(biāo)
?private int centerY;????//圓心Y坐標(biāo)
?private int width;?????//橢圓寬度
?private int height;?????//橢圓高度
?
?public Ellipse(int x, int y, int w, int h) ?//構(gòu)造方法
?{
??? super();??????//調(diào)用父類(lèi)的構(gòu)造方法1
??? centerX = x;
??? centerY = y;
??? width = w;
??? height = h;
?}
?
? public void draw()?????//覆蓋父類(lèi)的draw()方法
?{
??? System.out.println("draw a Ellipse");
?}
}
?
class Rectangle extends Shape???//定義子類(lèi)Rectangle
{
?private int left;?????//矩形左上角X坐標(biāo)
?private int top;?????//矩形左上角Y坐標(biāo)
?private int width;????//矩形長(zhǎng)度
?private int height;???//矩形寬度
?
?public Rectangle(int l, int t, int w, int h)?//構(gòu)造方法
?{
??? super(2);?????????? //調(diào)用父類(lèi)的構(gòu)造方法2
??? left = l;
??? top = t;
??? width = w;
??? height = h;
?}
?
?public void draw()????//覆蓋父類(lèi)的draw()方法
?{
??? System.out.println("draw a Rectangle");
?}
}
?
public class Inherit?????//定義類(lèi)Inherit
{
?public static void main(String args[])
??{
??? Ellipse ellipse = new Ellipse(30, 30, 50,60);?
???????????????????????? //創(chuàng)建子類(lèi)Ellipse的對(duì)象
??? ellipse.setLineSize(2);?
???????????????????????? //調(diào)用父類(lèi)方法重新設(shè)置lineSize 值為2
??? System.out.println("LineSize of ellipse : " +?ellipse.getLineSize());
?
??? Rectangle rectangle = new Rectangle(0, 0, 20, 30);?
???????????????????????? //創(chuàng)建子類(lèi)rectangle對(duì)象
??? rectangle.setLineSize(3);?
???????????????????????? //調(diào)用父類(lèi)方法重新設(shè)置lineSize屬性為3
??? System.out.println("LineSize of rectangle : " +?rectangle.getLineSize());
??? ellipse.draw();?????//訪(fǎng)問(wèn)子類(lèi)方法
??? rectangle.draw();????//訪(fǎng)問(wèn)子類(lèi)方法
?? }
}
程序運(yùn)行結(jié)果如下:
LineSize of ellipse : 2
LineSize of rectangle : 3
draw a Ellipse
draw a Rectangle
程序設(shè)計(jì)說(shuō)明:
(1)類(lèi)Shape中定義了所有子類(lèi)共同的成員變量lineSize(線(xiàn)寬),橢圓類(lèi)Ellipse和矩形類(lèi)Rectangle在繼承父類(lèi)成員變量的基礎(chǔ)上,又各自定義了自己的成員變量。
(2)父類(lèi)Shape中定義了畫(huà)圖方法draw(),子類(lèi)Ellipse和子類(lèi)Rectangle中由于各自形狀不同,畫(huà)圖方法draw()也不同,所以子類(lèi)Ellipse和Rectangle中重新定義了各自的draw()方法(即覆蓋了父類(lèi)的draw())。注意:子類(lèi)覆蓋父類(lèi)方法時(shí),參數(shù)個(gè)數(shù)和參數(shù)類(lèi)型必須相同。
(3)當(dāng)一個(gè)文件中包含有多個(gè)類(lèi)時(shí),源程序文件名應(yīng)該和定義為public類(lèi)型的類(lèi)名相同。
?
3、方法的三種繼承形式
?
??? 上節(jié)討論了子類(lèi)對(duì)父類(lèi)方法繼承的一般形式,本節(jié)我們進(jìn)一步總結(jié)子類(lèi)對(duì)父類(lèi)方法繼承的三種不同形式,以及系統(tǒng)中子類(lèi)對(duì)象訪(fǎng)問(wèn)方法的匹配原則和繼承在面向?qū)ο蟪绦蛟O(shè)計(jì)中的作用。
?
1.方法的三種繼承形式
??? 子類(lèi)對(duì)父類(lèi)方法繼承可以有三種不同形式:完全繼承、完全覆蓋和修改繼承。
??? (1)完全繼承。
??? 完全繼承是指子類(lèi)全部繼承父類(lèi)的方法。
????如果父類(lèi)中定義的方法完全適合于子類(lèi),則子類(lèi)就不需要重新定義該方法。子類(lèi)對(duì)父類(lèi)的繼承允許子類(lèi)對(duì)象直接訪(fǎng)問(wèn)父類(lèi)的方法,這就是子類(lèi)對(duì)父類(lèi)方法的完全繼承。
??? 例如例4.1中,如果子類(lèi)不重新定義draw()方法,則ellipse.draw()和rectangle.draw()訪(fǎng)問(wèn)的都是父類(lèi)中定義的draw()方法。
??? (2)完全覆蓋。
??? 完全覆蓋是指子類(lèi)重新定義父類(lèi)方法的功能,從而子類(lèi)中的同名方法完全覆蓋了父類(lèi)中的方法。
??? 如例4.1中,子類(lèi)重新定義了父類(lèi)的draw()方法,因此子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的就是子類(lèi)中重新定義的方法draw(),即ellipse.draw()和rectangle.draw()訪(fǎng)問(wèn)的都是子類(lèi)中定義的draw()方法。
??? (3)部分繼承。
??? 部分繼承是指子類(lèi)覆蓋父類(lèi)的方法,但子類(lèi)重新定義的方法中調(diào)用父類(lèi)中的同名方法,并根據(jù)問(wèn)題要求做部分修改。
?
【例4.2】修改繼承舉例。
class Shape???????????? //定義父類(lèi)Shape
{
?public void draw()
?{
??? System.out.println("Draw a Shape");
?}
}
?
class Ellipse extends Shape???? //定義子類(lèi)Ellipse
{
?public void draw()????//覆蓋父類(lèi)的draw()方法
?{
??? super.draw();
??? System.out.println("draw a Ellipse");
?}
}
?
class Rectangle extends Shape???//定義子類(lèi)Rectangle
{
?public void draw()????//覆蓋父類(lèi)的draw()方法
?{
??? super.draw();?????? //調(diào)用父類(lèi)的draw()方法
??? System.out.println("draw a Rectangle");?//修改部分
?}
}
?
public class CInherit???//定義類(lèi)Inherit
{
?public static void main(String args[])
??{
??? Ellipse ellipse = new Ellipse();????? //創(chuàng)建子類(lèi)Ellipse的對(duì)象
??? Rectangle rectangle = new Rectangle();//創(chuàng)建子類(lèi)rectangle對(duì)象
?
??? ellipse.draw();?????//訪(fǎng)問(wèn)子類(lèi)方法
??? rectangle.draw();???//訪(fǎng)問(wèn)子類(lèi)方法
?}
}
程序運(yùn)行結(jié)果如下:
Draw a Shape
draw a Ellipse
Draw a Shape
draw a Rectangle
?
程序設(shè)計(jì)說(shuō)明:
(1)子類(lèi)的draw()覆蓋了父類(lèi)的draw()方法,但子類(lèi)的draw()方法首先調(diào)用了父類(lèi)的draw()方法。子類(lèi)draw()方法調(diào)用父類(lèi)draw()方法的語(yǔ)句是:
??? super.draw();
(2)由于子類(lèi)方法在調(diào)用父類(lèi)方法的基礎(chǔ)上,又增加了子類(lèi)中需要補(bǔ)充修改的功能,所以子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的draw()方法,完成的功能是在父類(lèi)方法基礎(chǔ)上的補(bǔ)充或修改。
?
2.系統(tǒng)中子類(lèi)對(duì)象訪(fǎng)問(wèn)方法的匹配原則
??? 在Java語(yǔ)言(以及在所有的面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言)中,對(duì)象訪(fǎng)問(wèn)方法的匹配原則是:從對(duì)象定義的類(lèi)開(kāi)始,逐層向上匹配尋找對(duì)象要訪(fǎng)問(wèn)的方法。
??? 在完全繼承方式中,由于子類(lèi)中沒(méi)有定義draw()方法,所以系統(tǒng)自動(dòng)到它的直接父類(lèi)Shape中去匹配draw()方法,系統(tǒng)在父類(lèi)Shape中匹配上了draw()方法,所以子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的是父類(lèi)定義的draw()方法。在完全覆蓋方式中,由于子類(lèi)中定義了draw()方法,所以子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的是子類(lèi)定義的draw()方法。在修改繼承方式中,由于子類(lèi)中定義了draw()方法,所以子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的是子類(lèi)定義的draw()方法,由于子類(lèi)定義的draw()方法首先調(diào)用了父類(lèi)中定義的draw()方法,然后又增加了需要修改或補(bǔ)充的功能,所以子類(lèi)對(duì)象ellipse和rectangle訪(fǎng)問(wèn)的draw()方法,既包含了父類(lèi)draw()方法的功能,又包含了子類(lèi)修改或補(bǔ)充的功能。
?
3.繼承在面向?qū)ο蟪绦蛟O(shè)計(jì)中的作用
???
繼承在面向?qū)ο蟪绦蛟O(shè)計(jì)中有兩方面的意義:
??? 一方面,繼承性可以大大簡(jiǎn)化程序設(shè)計(jì)的代碼。我們可以把若干個(gè)相似類(lèi)所具有的共同成員變量和方法定義在父類(lèi)中,這樣這些子類(lèi)的設(shè)計(jì)代碼就可以大大減少。
??? 另一方面,繼承(特別是部分修改繼承和完全覆蓋繼承)使得大型軟件的功能修改和功能擴(kuò)充較傳統(tǒng)的軟件設(shè)計(jì)方法容易了許多。當(dāng)要對(duì)系統(tǒng)的一些原有功能進(jìn)行補(bǔ)充性修改或添加一些新的功能時(shí),可以重新設(shè)計(jì)原先類(lèi)的一個(gè)子類(lèi),利用部分修改繼承方法重新設(shè)計(jì)子類(lèi)中要補(bǔ)充性修改或添加的功能;當(dāng)要廢棄系統(tǒng)的一些原有功能,重新設(shè)計(jì)完全不同的新的功能時(shí),可以重新設(shè)計(jì)原先類(lèi)的一個(gè)子類(lèi),利用完全覆蓋繼承方法重新設(shè)計(jì)子類(lèi)中的功能。
??? 繼承性是面向?qū)ο蠓椒ǖ囊粋€(gè)非常重要的特點(diǎn)。這是因?yàn)槔^承性使得我們可以根據(jù)問(wèn)題的特征,把若干個(gè)類(lèi)設(shè)計(jì)成繼承關(guān)系。而類(lèi)的繼承關(guān)系和人類(lèi)認(rèn)識(shí)客觀(guān)世界的過(guò)程和方法基本吻合,從而使得人們能夠用和認(rèn)識(shí)客觀(guān)世界一致的方法來(lái)設(shè)計(jì)軟件。
?
4、方法的多態(tài)性
?
1.對(duì)象的動(dòng)態(tài)綁定和方法的多態(tài)性
???
方法的多態(tài)性是面向?qū)ο蟪绦虻牧硪粋€(gè)重要特點(diǎn)。方法的多態(tài)(polymorphism)是指若以父類(lèi)定義對(duì)象,并動(dòng)態(tài)綁定對(duì)象,則該對(duì)象的方法將隨綁定對(duì)象不同而不同。
??? 在只定義對(duì)象、沒(méi)有分配內(nèi)存空間時(shí),如下圖(a)所示,對(duì)象名中并沒(méi)有存放實(shí)際對(duì)象的首地址。在為已定義的對(duì)象分配了內(nèi)存空間后,如下圖(b)所示,對(duì)象名中存儲(chǔ)的就是對(duì)象的內(nèi)存空間的首地址。對(duì)象名和實(shí)際對(duì)象的這種聯(lián)系稱(chēng)作對(duì)象的綁定(binding)。
?
?
??
??? Java語(yǔ)言還支持對(duì)象的動(dòng)態(tài)綁定。所謂對(duì)象的動(dòng)態(tài)綁定,是指定義為類(lèi)樹(shù)上層的對(duì)象名,可以綁定為所定義層類(lèi)以及下層類(lèi)的對(duì)象。這樣,當(dāng)對(duì)象動(dòng)態(tài)綁定為哪一層子類(lèi)對(duì)象時(shí),其方法就調(diào)用那一層子類(lèi)的方法。因此,對(duì)象的動(dòng)態(tài)綁定和類(lèi)的繼承相結(jié)合就使對(duì)象的方法具有多態(tài)性。
?
【例4.3】方法的多態(tài)性示例。
class Shape??????????????//定義父類(lèi) Shape
{
?public void draw()?????//父類(lèi)的draw()方法
??{
????System.out.println("Draw a Shape");
??}
}
?
class Circle extends Shape???? //定義子類(lèi)Circle
{
?public void draw()?????//覆蓋父類(lèi)的draw()方法
?{
??? ??System.out.println("draw a Circle");
?}
}
?
class Ellipse extends Circle???//定義子類(lèi)Ellipse
{
?public void draw()?????//覆蓋父類(lèi)的draw()方法
??? {
??? ??System.out.println("draw a Ellipse");
?}
}
?
public class FInherit???????//定義類(lèi)FInherit
{
?public static void main(String args[])
?{
??Shape s= new Shape();???? //動(dòng)態(tài)綁定為類(lèi)Shape對(duì)象
??Shape c = new Circle();???//動(dòng)態(tài)綁定為類(lèi)Circle對(duì)象
??Shape e = new Ellipse();??//
動(dòng)態(tài)綁定為類(lèi)Ellipse對(duì)象
?
??????? s.draw();??????//訪(fǎng)問(wèn)父類(lèi)方法
??????? c.draw();??????//訪(fǎng)問(wèn)一級(jí)子類(lèi)方法
??????? e.draw();??????//訪(fǎng)問(wèn)二級(jí)子類(lèi)方法???????
?}
}
程序運(yùn)行結(jié)果如下:
Draw a Shape
draw a Circle
draw a Ellipse
程序說(shuō)明:
(1)類(lèi)Shape是父類(lèi), 類(lèi)Circle是類(lèi)Shape的直接子類(lèi),類(lèi)Ellipse是類(lèi)Circle的直接子類(lèi)。這三個(gè)類(lèi)中都定義了draw()方法。子類(lèi)中的draw()方法覆蓋了父類(lèi)中的同名方法。
(2)FInherit 類(lèi)的main()方法中,定義了三個(gè)對(duì)象,三個(gè)對(duì)象s、c和e都定義為Shape類(lèi),但對(duì)象s動(dòng)態(tài)綁定為Shape類(lèi)的對(duì)象,對(duì)象c動(dòng)態(tài)綁定為Circle類(lèi)的對(duì)象,對(duì)象e動(dòng)態(tài)綁定為Ellipse類(lèi)的對(duì)象。這樣,語(yǔ)句s.draw()調(diào)用的就是Shape類(lèi)的方法draw(),語(yǔ)句c.draw()調(diào)用的就是Circle類(lèi)的方法draw(),語(yǔ)句e.draw()調(diào)用的就是Ellipse類(lèi)的方法draw()。
?
2.方法多態(tài)性的用途
???
方法的多態(tài)性在程序設(shè)計(jì)中非常有用。例如Java API語(yǔ)言包的Vector類(lèi),Vector類(lèi)中定義的一個(gè)方法如下:
??? copyInto(Object[] anArray)
??? 該方法的功能是把當(dāng)前對(duì)象的一個(gè)成分復(fù)制給對(duì)象數(shù)組anArray。其參數(shù)anArray定義為Object類(lèi)的數(shù)組,由于Object類(lèi)是所有類(lèi)的根(即最上層的類(lèi)),所以,該方法可用于任何類(lèi)的對(duì)象。例如,程序中可以像下面這樣使用Vector類(lèi)的copyInto()方法:
??? Vector v = new Vector();????????????//定義并創(chuàng)建Vector類(lèi)的對(duì)象v
??? String[] s = new String[v.size()];??//定義并創(chuàng)建String類(lèi)的對(duì)象s
??? v.copyInto(s);????????????????????? //把對(duì)象v的當(dāng)前成分復(fù)制給對(duì)象s
??? 上面語(yǔ)句段的最后一句將把對(duì)象v的當(dāng)前成分復(fù)制給對(duì)象s。如果沒(méi)有方法的多態(tài)性,若要定義Vector類(lèi)的copyInto()方法適合所有類(lèi)的對(duì)象時(shí),就要把該方法用不同類(lèi)的參數(shù)重載很多個(gè);而方法的多態(tài)性支持Vector類(lèi)的copyInto()方法用Object類(lèi)參數(shù)(Object [] anArray)定義一次,就可以適合于所有類(lèi)的對(duì)象了。
?
?
3、抽象類(lèi)和最終類(lèi)
?
??? 在類(lèi)的定義中,除了可說(shuō)明該類(lèi)的父類(lèi)外,還可以說(shuō)明該類(lèi)是否是最終類(lèi)或抽象類(lèi)。
?
1、抽象類(lèi)
?
??? 類(lèi)中允許定義抽象方法。所謂抽象方法是指只有方法的定義,沒(méi)有方法的實(shí)現(xiàn)體的方法。Java語(yǔ)言用關(guān)鍵字abstract來(lái)聲明抽象方法。例如:
??? abstract?void?draw()
??? 則聲明類(lèi)中的draw()方法為抽象方法。但是,需要說(shuō)明的是:
??? (1)構(gòu)造方法不能被聲明為抽象的。
??? (2)abstract和static不能同時(shí)存在,即不能有abstract static方法。
??? 包含抽象方法的類(lèi)稱(chēng)為抽象類(lèi)。換句話(huà)說(shuō),任何包含抽象方法的類(lèi)必須被聲明為抽象類(lèi)。因?yàn)槌橄箢?lèi)中包含沒(méi)有實(shí)現(xiàn)的方法,所以抽象類(lèi)是不能直接用來(lái)定義對(duì)象。Java語(yǔ)言用關(guān)鍵字abstract來(lái)聲明抽象類(lèi),例如:
??? abstract?class?Shape?
??? 則聲明類(lèi)Shape為抽象類(lèi)。
??? 在程序設(shè)計(jì)中,抽象類(lèi)主要用于定義為若干個(gè)功能類(lèi)同的類(lèi)的父類(lèi)。
【例4.4】抽象類(lèi)舉例。
問(wèn)題描述:設(shè)計(jì)橢圓類(lèi)Ellipse和矩形類(lèi)Rectangle,要求這兩個(gè)類(lèi)都包含一個(gè)畫(huà)圖方法draw()。
設(shè)計(jì)分析:橢圓類(lèi)Ellipse和矩形類(lèi)Rectangle有許多成員變量和方法相同,因此,可以先設(shè)計(jì)一個(gè)它們的共同的父類(lèi)(也稱(chēng)基類(lèi))Shape,并把畫(huà)圖方法draw()定義在父類(lèi)中。但是,由于父類(lèi)Shape只是抽象的形狀,畫(huà)圖方法draw()無(wú)法實(shí)現(xiàn),所以,父類(lèi)中的畫(huà)圖方法draw()只能定義為抽象方法,而包含抽象方法的Shape類(lèi)也只能定義為抽象類(lèi)。
abstract class Shape???????????//定義抽象類(lèi) Shape
{
?public abstract void draw();??//定義抽象方法
}
?
class Ellipse extends Shape????//定義子類(lèi)Ellipse
{
?public void draw()?????????? //實(shí)現(xiàn)draw()方法
?{
??? System.out.println("draw a Ellipse");
?}
}
?
class Rectangle extends Shape??//定義子類(lèi)Rectangle
{
?public void draw()????????? ?//實(shí)現(xiàn)draw()方法
?{
??? System.out.println("draw a Rectangle");
?}
}
?
public class AInherit???????? //定義類(lèi)Inherit
{
?public static void main(String args[])
??{
??? Ellipse ellipse = new Ellipse();?????? //創(chuàng)建子類(lèi)Ellipse的對(duì)象
??? Rectangle rectangle = new Rectangle();//創(chuàng)建子類(lèi)rectangle對(duì)象
?
??? ellipse.draw();?????//訪(fǎng)問(wèn)子類(lèi)ellipse的方法
??? rectangle.draw();????//訪(fǎng)問(wèn)子類(lèi)rectangle的方法
?? }
}
上述例子說(shuō)明:
(1)在一個(gè)軟件中,抽象類(lèi)一定是某個(gè)類(lèi)或某些類(lèi)的父類(lèi)。
(2)若干個(gè)抽象類(lèi)的子類(lèi)要實(shí)現(xiàn)一些同名的方法。
在后面討論的Java API中,系統(tǒng)的許多類(lèi)都是用上面形式的結(jié)構(gòu)定義和實(shí)現(xiàn)的。
?
2、最終類(lèi)
?
??? 最終類(lèi)是指不能被繼承的類(lèi),即不能再用最終類(lèi)派生子類(lèi)。在Java語(yǔ)言中,如果不希望某個(gè)類(lèi)被繼承,可以聲明這個(gè)類(lèi)為最終類(lèi)。最終類(lèi)用關(guān)鍵字final來(lái)說(shuō)明。例如:
??? public final class C
??? 就定義類(lèi)C為最終類(lèi)。
??? 如果創(chuàng)建最終類(lèi)似乎不必要,而又想保護(hù)類(lèi)中的一些方法不被覆蓋,可以用關(guān)鍵字final來(lái)指明那些不能被子類(lèi)覆蓋的方法,這些方法稱(chēng)為最終方法。例如:
??? public class A
????{
????? public final void M();
??? }
??? 就在類(lèi)A中定義了一個(gè)最終方法M(),任何類(lèi)A的子類(lèi)都不能重新定義方法M()。
??? 在程序設(shè)計(jì)中,最終類(lèi)可以保證一些關(guān)鍵類(lèi)的所有方法,不會(huì)在以后的程序維護(hù)中,由于不經(jīng)意的定義子類(lèi)而被修改;最終方法可以保證一些類(lèi)的關(guān)鍵方法,不會(huì)在以后的程序維護(hù)中,由于不經(jīng)意的定義子類(lèi)和覆蓋子類(lèi)的方法而被修改。
??? 需要注意的是:一個(gè)類(lèi)不能既是最終類(lèi)又是抽象類(lèi),即關(guān)鍵字abstract和final不能合用。在類(lèi)聲明中,如果需要同時(shí)出現(xiàn)關(guān)鍵字public和abstract(或final),習(xí)慣上,public放在abstract(或final)的前面。
?
?
4、接口
?
??? 面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言的一個(gè)重要特性是繼承。繼承是指子類(lèi)可以繼承父類(lèi)的成員變量和方法。如果子類(lèi)只允許有一個(gè)直接父類(lèi),這樣的繼承稱(chēng)作單繼承。如果子類(lèi)允許有一個(gè)以上的直接父類(lèi),這樣的繼承稱(chēng)作多繼承。單繼承具有結(jié)構(gòu)簡(jiǎn)單,層次清楚,易于管理,安全可靠的特點(diǎn)。多繼承具有功能強(qiáng)大的特點(diǎn)。
??? Java語(yǔ)言只支持單繼承機(jī)制,不支持多繼承。一般情況下,單繼承就可以解決大部分子類(lèi)對(duì)父類(lèi)的繼承問(wèn)題。但是,當(dāng)問(wèn)題復(fù)雜時(shí),若只使用單繼承,可能會(huì)給設(shè)計(jì)帶來(lái)許多麻煩。Java語(yǔ)言解決這個(gè)問(wèn)題的方法是使用接口。
??? 接口和抽象類(lèi)非常相似,都是只定義了類(lèi)中的方法,沒(méi)有給出方法的實(shí)現(xiàn)。
??? Java語(yǔ)言不僅規(guī)定一個(gè)子類(lèi)只能直接繼承自一個(gè)父類(lèi),同時(shí)允許一個(gè)子類(lèi)可以實(shí)現(xiàn)(也可以說(shuō)繼承自)多個(gè)接口。由于接口和抽象類(lèi)的功能類(lèi)同,因此,Java語(yǔ)言的多繼承機(jī)制是借助于接口來(lái)實(shí)現(xiàn)的。
?
1、定義接口
?
??? 接口的定義格式為:
??? <修飾符> interface<接口名>
??? {
????? 成員變量1 =?<初值1>;
????? 成員變量2 =?<初值2>;
????? ……
????? 方法1;
?????方法2;
????? ……
??? }
??? 其中,<修飾符>可以是public,也可以缺省。當(dāng)為缺省時(shí),接口只能被與它處在同一包中的方法訪(fǎng)問(wèn);當(dāng)聲明為public時(shí),接口能被任何類(lèi)的方法訪(fǎng)問(wèn)。<接口名>是接口的名字,可以是任何有效的標(biāo)識(shí)符。例如,
??? public interface PrintMessage?
??? {
????? public int count = 10;
????? public void printAllMessage();
????? public void printLastMessage();
????? public void printFirstMessage();
??? }
??? 就定義了一個(gè)接口PrintMessage。接口中的方法(printAllMessage()等)只有方法定義,沒(méi)有方法實(shí)現(xiàn)。所以接口實(shí)際上是一種特殊的抽象類(lèi)。
??? 需要說(shuō)明的是:
????(1)若接口定義為默認(rèn)型訪(fǎng)問(wèn)權(quán)限,則接口中的成員變量全部隱含為final static型。這意味著它們不能被實(shí)現(xiàn)接口方法的類(lèi)改變,并且為默認(rèn)訪(fǎng)問(wèn)權(quán)限。
????(2)接口中定義的所有成員變量都必須設(shè)置初值。
????(3)若接口定義為public型訪(fǎng)問(wèn)控制,則接口中的方法和成員變量全部隱含為public型。
????(4)當(dāng)接口保存于文件時(shí),其文件命名方法和保存類(lèi)的文件命名方法類(lèi)同。即保存接口的文件名必須與接口名相同。一個(gè)文件可以包含若干個(gè)接口,但最多只能有一個(gè)接口定義為public,其他的接口必須為默認(rèn)。
?
2、實(shí)現(xiàn)接口
?
??? 一旦定義了一個(gè)接口,一個(gè)或更多的類(lèi)就能實(shí)現(xiàn)這個(gè)接口。為了實(shí)現(xiàn)接口,類(lèi)必須實(shí)現(xiàn)定義在接口中的所有方法。每個(gè)實(shí)現(xiàn)接口的類(lèi)可以自由地決定接口方法的實(shí)現(xiàn)細(xì)節(jié)。
??? 定義類(lèi)時(shí)實(shí)現(xiàn)接口用關(guān)鍵字implements。一個(gè)類(lèi)只能繼承一個(gè)父類(lèi),但可以實(shí)現(xiàn)若干個(gè)接口。因此,類(lèi)定義的完整格式是:
??? [<修飾符>]class<類(lèi)名> [extends<父類(lèi)名>] [implements <接口名1>,<接口名2>,……]
??? 其中,關(guān)鍵字implements后跟隨的若干個(gè)接口名表示該類(lèi)要實(shí)現(xiàn)的接口;如果要實(shí)現(xiàn)多個(gè)接口,則用逗號(hào)分隔開(kāi)接口名。
【例4.5】編寫(xiě)一個(gè)實(shí)現(xiàn)接口PrintMessage(為簡(jiǎn)化設(shè)計(jì)代碼,去掉其中的成員變量定義)的類(lèi),并編寫(xiě)一個(gè)測(cè)試程序進(jìn)行測(cè)試。
程序設(shè)計(jì)如下:
//接口文件PrintMessage.java
public interface PrintMessage
{
?public int count = 10;
? public void printAllMessage();
? public void printLastMessage();
? public void printFirstMessage();
}
?
//實(shí)現(xiàn)接口的文件MyInter.java
public class MyInter implements PrintMessage?//實(shí)現(xiàn)接口的類(lèi)MyInter
{
? private String[] v;????//類(lèi)中的成員變量v
? private int i;????????//類(lèi)中的成員變量i
?
? public MyInter()??????// MyInter類(lèi)的構(gòu)造方法
? {
????v = new String[3];
????i = 0;
??? ??this.putMessage("Hello world!");?? //使用MyInter類(lèi)的方法
??? ??this.putMessage("Hello China!");
??? ??this.putMessage("Hello XSYU!");
? ?}
?
? public void putMessage(String str)????//類(lèi)中的方法
??{?
?????v[i++] = str;
? }
?
? public void printAllMessage()????????? //實(shí)現(xiàn)接口中的方法
??{
?????for(int k = 0; k < v.length; k++)
?????{
???????System.out.println(v[k]);
?????}
??}
??public void printLastMessage()???????? //實(shí)現(xiàn)接口中的方法
??{
?????System.out.println(v[v.length - 1]);
??}
?
??public void printFirstMessage()??????? //實(shí)現(xiàn)接口中的方法
??{
?????System.out.println(v[0]);
??}
?
??public static void main(String[] args)??
??{
?????MyInter mi=new MyInter();???????? //定義MyInter類(lèi)的對(duì)象
?????System.out.println("print all messages");
?????mi.printAllMessage();???????????? //使用實(shí)現(xiàn)了的接口方法
?????System.out.println("print the first messages");
?????mi.printFirstMessage();????????? ?//使用實(shí)現(xiàn)了的接口方法
?????System.out.println("print the last messages");
?????mi.printLastMessage();??????????? //使用實(shí)現(xiàn)了的接口方法
??}
}
程序的運(yùn)行結(jié)果如下:
print all messages
Hello world!
Hello China!
Hello XSYU!
print the first messages
Hello world!
print the last messages
Hello XSYU!
??? 程序說(shuō)明:在定義類(lèi)MyInter時(shí),后邊跟有implements PrintMessage,表示該類(lèi)中要實(shí)現(xiàn)接口PrintMessage。此時(shí)類(lèi)MyInter中必須實(shí)現(xiàn)接口PrintMessage中定義的三個(gè)方法。由于類(lèi)MyInter隱含繼承了類(lèi)Object,現(xiàn)在又實(shí)現(xiàn)了接口PrintMessage,所以類(lèi)MyInter是一個(gè)多繼承。可見(jiàn),接口支持了Java的多繼承。
?
3、系統(tǒng)定義的接口
?
??? Java API中定義了許多接口,一旦安裝了JDK運(yùn)行環(huán)境,就可以像使用用戶(hù)自己定義的接口一樣使用系統(tǒng)定義的接口。例如,Enumeration是系統(tǒng)定義的一個(gè)接口。Enumeration接口的定義如下:
??? public interface Enumeration?
??? {
????? Object nextElement();????? ?//返回后續(xù)元素
????? boolean hasMoreElements();??//是否還有后續(xù)元素
??? }
??? 許多系統(tǒng)定義的類(lèi)都實(shí)現(xiàn)了Enumeration接口。如有必要,用戶(hù)自己定義的類(lèi)也可以實(shí)現(xiàn)Enumeration接口。
-The End-