官术网_书友最值得收藏!

3.3 繼承

前面創建的CAuto類和CAutoFactory類已經定義了不少代碼,而且由這兩個類生產的SUV車型還不錯。接下來,還要給SUV裝上武器,用于開發軍用車型。面向對象編程中,這些工作并不需要完全重新開始,而是在現有類的基礎上進行改造和擴展。下面的代碼(CAssaultVehicle.java文件)創建CAssaultVehicle類。

    package com.caohuayu.javademo;
public class CAssaultVehicle extends CAuto {
}

這里使用了extends關鍵字,其含義是擴展,但在面向對象編程概念中,更多情況下會說CAssaultVehicle類繼承于CAuto類,即CAssaultVehicle類是CAuto類的子類,而CAuto類稱為CAssaultVehicle類的超類(或基類、父類)。

CAssaultVehicle類中,只是讓它繼承了CAuto類,并沒有定義任何內容。CAssaultVehiche類有什么功能呢?不如測試一下,如下面的代碼所示。

    public static void main(String[] args) {
        CAssaultVehicle av = new CAssaultVehicle();
        av.model = "突擊者";
        av.setDoors(5);
        av.moveTo(10, 99);
    }

圖3-10 類的繼承

代碼執行結果如圖3-10所示。

示例中,雖然CAssaultVehicle類中沒有定義任何成員,但它已經從CAuto類中繼承了不少東西,主要包括無參數的構造函數和非私有的成員(非private定義的成員),如model字段、setDoors()方法、getDoors()方法、moveTo()方法等。可以發現,繼承的作用還是挺大的。

進一步討論繼承之前,需要注意一個問題,如果一個類不希望被繼承,可以在定義時使用final關鍵字。下面是一個簡單的示例。

    public final class C1 {
    //
    }

這樣,C1類就不能被繼承了,例如,下面的代碼就會提示錯誤。

    public class C2 extends C1 {
        //
    }

另外一個需要注意的問題是,在Java中,不像在C++中那樣,子類可以同時繼承多個超類。也就是說,一個類同時只能有一個直接超類。

了解了這些,接下來將討論關于繼承的更多內容。

3.3.1 java.lang.Object類

定義在java.lang包的Object類有什么特殊之處?它可是Java中其他類的終極超類,也是唯一一個沒有超類的類型。如果一個類沒有明確指定超類,則默認繼承于Object類。

對于前面創建的CAuto類、CAssaultVehicle類,以及Object類,它們的繼承關系如圖3-11所示。

那么,是不是在所有類中都可以使用Object類的非私有成員呢?答案是肯定的,例如,下面的代碼使用了一些CAuto類中沒有定義的成員。

    public static void main(String[] args) {
    CAuto auto = new CAuto();
        CAssaultVehicle av = new CAssaultVehicle();
        System.out.println(auto.toString());
        System.out.println(av.getClass().getSuperclass().toString());
    }

第一個輸出語句使用toString()方法顯示了auto對象的信息。第二個輸出語句顯示了av對象所屬類型的超類信息。代碼執行結果如圖3-12所示。

圖3-11 類繼承的層次

圖3-12 繼承Object類成員

可以看到,在Java代碼中動態處理對象和類的信息也是比較方便的,稍后還會討論相關內容。下面先回到CAssaultVehicle類,前面提到要在車上安裝武器。

3.3.2 擴展與重寫

如果CAssaultVehicle類只是簡單地繼承CAuto類,繼承的意義就不大了。實際上,在子類中可以擴展超類功能,或者對超類的功能進行重寫。

首先考慮CAssaultVehicle類的構造函數。如果在子類中沒有定義構造函數,默認會繼承超類中的無參數構造函數;如果在子類中定義了一個構造函數,就不能直接使用超類的構造函數創建對象了。

那么,在CAuto類中創建的構造函數就無用武之地了嗎?當然不是,只不過需要在CAssaultVehicle類中加個“外殼”而已。例如,下面的代碼在CAssaultVehicle類中添加了一個無參數的構造函數。

代碼中,使用super關鍵字調用超類的構造函數,分別指定型號和車門數量,這里調用的就是CAuto類中的CAuto(String m, int d)構造函數。

下面的代碼測試CAssaultVehicle對象的創建。

    public static void main(String[] args) {
        CAssaultVehicle av = new CAssaultVehicle();
        av.moveTo("9號地區");
    }

圖3-13 調用超類構造函數

代碼執行結果如圖3-13所示。

通過以上示例可以看到,創建構造函數的過程中,通過this、super關鍵字,可以合理地重用當前類或超類中的構造函數,使用靈活的方式來構建對象。

如果需要擴展CAssaultVehicle類的功能,直接寫出來即可。下面的代碼在CAssaultVehicle類中添加一個字段和一個方法。

代碼中,創建了weapon字段和attack()方法。下面測試這兩個新成員的使用。

    public static void main(String[] args) {
        CAssaultVehicle av = new CAssaultVehicle();
        av.weapon = "12.7mm機槍";
        av.attack("靶標");
    }

代碼執行結果如圖3-14所示。

此外,子類中如果需要重新實現超類中的成員,也可以直接定義。然后,還可以使用super關鍵字訪問超類中的成員。下面的代碼在CAssaultVehicle類中重寫moveTo(String target)方法。

這里使用super關鍵字調用了超類(CAuto類)中的同名方法。下面的代碼演示了新方法的使用。

    public static void main(String[] args) {
        CAssaultVehicle av = new CAssaultVehicle();
        av.moveTo("X地區");
    }

代碼執行結果如圖3-15所示。

圖3-14 擴展類成員

圖3-15 重寫超類方法

3.3.3 訪問級別

前面的示例中已經多次使用了訪問級別的控制,這里簡單總結一下Java中的常用訪問級別。

□ private,定義私有成員,即成員只能在其定義的類中訪問。

□ protected,受保護的成員,它可以在定義的類或子類中訪問。

□ public,公共成員,它可以供類的外部代碼調用。

此外,當成員不使用訪問控制關鍵字時,稱為默認(default)訪問級別。默認訪問級別的成員與public有些相似,可以在類的外部調用,但是默認訪問級別的成員只能在其定義的包中使用。

一般情況下,出于數據的安全性,類成員的訪問級別應遵循最小原則,即優先使用private級別。然后,根據需要定義為protected或public級別。對于默認訪問級別,它看上去并不直觀,容易讓人感到困惑,所以需要熟悉其含義,并在開發中合理使用。

3.3.4 instanceof運算符

instanceof運算符用于判斷一個對象是否為某個類的實例。在繼承關系中,需要注意它的靈活使用。

下面的代碼創建一個CAuto對象和一個CAssaultVehicle對象。

分別來看四個輸出語句。

第一個輸出語句中,auto對象定義為CAuto類的實例,所以顯示為true,這個比較容易理解。

第二個輸出語句中,av對象定義為CAssaultVehicle類的實例,但CAssaultVehiclee類定義為CAuto類的子類,所以av對象完全可以按CAuto對象的方式進行操作。

第三個輸出語句中,實際上,所有對象在此都會顯示為true,因為Object類是終極超類。

第四個輸出語句中,auto對象不能使用CAssaultVehicle類中的新增成員,不能按CAssaultVehicle對象的方式進行工作,所以顯示為false。

通過以上示例可以看到instanceof運算符的一些應用特點。

□ 所有對象與Object類的運算結果都是true。

□ 對象與其類型或其超類的運算結果為true。

3.3.5 抽象類與抽象方法

定義方法時使用abstract關鍵字,方法就定義為抽象方法。抽象方法并不需要包含方法體,它必須由類的子類來實現。同時,當一個類中包含抽象方法時,這個類應該定義為抽象類。

例如,下面的代碼(CPlaneBase.java文件)創建一個名為CPlaneBase的抽象類。

這里,在CPlaneBase類中定義一個字段、一個構造函數和兩個抽象方法,其中,抽象方法中并沒有使用“{”和“}”符號定義方法體,而是直接以分號結束。

請注意,抽象類是不能創建實例的,例如,下面的代碼就不能正確執行。

    CPlaneBase plane = new CPlaneBase();  // 錯誤

接下來,創建一個CPlaneBase類的子類,如下面的代碼(CFighter.java文件)所示。

下面的代碼測試CFighter類的使用。

    public static void main(String[] args) {
        CFighter f = new CFighter();
        System.out.println(f.model);
        System.out.println(f.getWeapon());
      System.out.println(f.getMaxSpeed());
    }

代碼執行結果如圖3-16所示。

實際應用中,抽象類更像是標準制定者,它可以定義一系列抽象方法,然后讓其子類去具體實現,從而創建具有相同成員但實現各有不同的類型。

下面的代碼(CConveyor.java文件)再創建一個CConveyor類,同樣,它定義為CPlaneBase類的子類。

圖3-16 繼承抽象類

下面的代碼來測試這幾個類的使用。

    public static void main(String[] args) {
        CPlaneBase plane = new CFighter();
        System.out.println(plane.model);
        System.out.println(plane.getWeapon());
        System.out.println(plane.getMaxSpeed());
        //
        System.out.println("*** 飛機變形 ***");
        //
        plane = new CConveyor();
        System.out.println(plane.model);
        System.out.println(plane.getWeapon());
        System.out.println(plane.getMaxSpeed());
    }

代碼中,plane對象定義為CPlaneBase類型,但它不能實例化為CPlaneBase類的實例。首先,將plane實例化為CFighter類的對象,顯示信息后,又將plane對象實例化為CConveyor類的對象并顯示信息。代碼執行結果如圖3-17所示。

實際上,對于標準的制定者,接口(interface)會更加純粹,第4章將討論相關內容。

圖3-17 抽象類的綜合測試

主站蜘蛛池模板: 文安县| 贞丰县| 武冈市| 金沙县| 正定县| 靖西县| 农安县| 邹平县| 石景山区| 溧水县| 新建县| 黎平县| 衡水市| 达日县| 忻州市| 霍州市| 克拉玛依市| 寿光市| 湖南省| 宁阳县| 顺义区| 陵水| 石河子市| 镇坪县| 玛曲县| 乐昌市| 庐江县| 鄱阳县| 临朐县| 五台县| 宝丰县| 额尔古纳市| 日喀则市| 阳江市| 肥城市| 贵阳市| 金坛市| 凤山市| 华宁县| 丹江口市| 通道|