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