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