- Java與Android移動應用開發:技術、方法與實踐
- 曹化宇
- 2670字
- 2019-12-06 15:39:10
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 抽象類的綜合測試
- Android項目開發入門教程
- INSTANT MinGW Starter
- Practical Windows Forensics
- Access 2010數據庫基礎與應用項目式教程(第3版)
- 算法訓練營:提高篇(全彩版)
- Hands-On Automation Testing with Java for Beginners
- iOS自動化測試實戰:基于Appium、Python與Pytest
- Learning AWS
- Hands-On Robotics Programming with C++
- 數字媒體技術概論
- Python數據預處理技術與實踐
- Learning Shiny
- Wearable:Tech Projects with the Raspberry Pi Zero
- RESTful Web API Design with Node.js(Second Edition)
- 計算思維與Python編程