JavaSE第10篇:面向物件之繼承
目錄
本篇我們將繼續學習面向物件程式設計,在之前我們已經學習過了面向物件之封裝,知道了如何定義一個標準的類及如何建立和使用物件。面向物件有三大特徵,封裝、繼承、多型。那麼接下來,我們將會學習面向物件程式設計的繼承。
第一章:繼承
1.1-繼承概述(瞭解)
什麼是繼承?
繼承,指的是事物與事物之間的關係。
在生活中,我們所理解的“繼承”更多的是“子承父業”,就是兒子與父親之間存在“繼承”關係。
在Java程式設計中,我們用類來描述事物,那麼在面向物件程式設計中,“繼承”指的是類與類之間的關係,通常是子類和父類之間的關係,子類可以繼承父類中的非私有成員(屬性和方法)。
為什麼要有繼承?
繼承有什麼好處呢?
在生活中,很顯然,兒子繼承父親的財產,可以少奮鬥幾十年甚至幾輩子。
在程式設計中,繼承有什麼作用呢?我們先來看一個需求,需求如下:
- 用面向物件的方式,描述幾種動物,狗、貓、牛
- 狗,有名字、年齡、性別、毛色、愛吃食物、會看門
- 貓,有名字、年齡、性別、毛色、愛吃食物、會撒嬌
- 牛,有名字、年齡、性別、毛色、愛吃食物、會耕地
此時面臨這個需求時,我們通常會定義三種類來描述,描述如下:
我們觀察可以發現,會存在這樣一種現象:貓、狗、牛,存在一些共性的成員,這些共性都分別在不同的類中定義了。
問題:這樣在程式設計中,就出現的程式碼冗餘(重複),若是以後有更多的動物種類出現,還是要重新定義它們的共性成員,在整個工程看來,程式碼將會變得越來越冗餘,越來越臃腫,不易於程式後期的維護。那如何解決呢?
解決:此時就可以使用繼承機制解決程式碼的冗餘問題,抽取共性,讓共性複用。抽取的方式就是,抽象出更高階的類(超類、父類),把共性定義在父類中,讓其他也要擁有這些共性成員的類作為子類繼承父類。
對於上述需求,我們可以抽象出它們的父類,動物類。它們都是動物,動物都有屬性-名字、年齡、毛色、性別,都是吃貨,最終類的定義描述如下:
所以,在面向物件程式設計中,繼承的作用:
- 提高程式碼的複用性,減少程式碼冗餘。
- 繼承是多型的基礎(後續講解)
總結
- 什麼是繼承:
- 繼承就是,子類與父類之間的關係,子類可以繼承父類中的成員。
- 繼承的作用:
- 提高程式碼複用性,減少程式碼冗餘。
1.2-繼承的格式(記憶)
通過上述講解,我們知道了什麼是繼承以及繼承的作用,接下來我們來學習一下Java中定義繼承的格式。
格式
關鍵字:extends
class 父類 {
...
}
class 子類 extends 父類 {
...
}
示例
父類:動物類
package www.penglei666.com.demo03;
/**
* 動物類,父類
*/
public class Animal {
String name;
int age;
String color;
String gender;
public void eat(){
System.out.println("我是吃貨!");
}
}
子類:狗類
package www.penglei666.com.demo03;
/**
* 狗類,子類,繼承了Animal
*/
public class Dog extends Animal {
public void alert(){
System.out.println("警告,我是看門專家");
}
}
測試類:Test
package www.penglei666.com.demo03;
public class Test {
public static void main(String[] args) {
Dog wc = new Dog();
// 可以使用父類中的屬性:name、age、gender、color
wc.name = "旺財";
wc.age = 10;
wc.gender = "公";
wc.color = "yellow";
// 可以呼叫父類中的方法:eat,也可以呼叫自己的方法
wc.eat(); // 我是吃貨!
wc.alert(); // 警告,我是看門專家
}
}
在上述程式碼中,Dog類通過extends關鍵字繼承了Animal類,這樣Dog類便是Animal類的子類。
從執行結果不難看出,子類雖然沒有定義name、age、gender等屬性和eat方法,但是卻能操作這幾個成員。這就說明,子類在繼承父類的時候,會自動擁有父類的成員。
1.3-super關鍵字(記憶)
若是父類中的成員和子類中的成員重名,子類物件在呼叫重名的成員時,會使怎樣的現象呢?
子類和父類成員重名時
父類:Animal
package www.penglei666.com.demo03;
/**
* 動物類,父類
*/
public class Animal {
int age = 10;
}
子類:Dog
package www.penglei666.com.demo03;
/**
* 狗類,子類,繼承了Animal
*/
public class Dog extends Animal {
int age = 11;
public void printAge(){
System.out.println("年齡:" + this.age);
}
}
測試類:Test
package www.penglei666.com.demo03;
public class Test {
public static void main(String[] args) {
Dog wc = new Dog();
wc.printAge(); // 輸出結果:年齡:11
}
}
通過輸出結果可以發現,輸出的並不是父類中的結果值,而是子類自己的。
那麼,如何在重名的情況下,如何訪問到父類中的成員呢?此時可以使用關鍵字:super
我們之前,學習過this關鍵字,表示代表呼叫者本身的引用,而super關鍵字,表示父類的引用。
super 關鍵字的使用
使用格式:super.父類成員變數名
修改子類程式碼:Dog類
package www.penglei666.com.demo03;
/**
* 狗類,子類,繼承了Animal
*/
public class Dog extends Animal {
int age = 11;
public void printAge(){
System.out.println("父age:" + super.age);
System.out.println("子age:" + this.age);
}
}
測試類:Test
package www.penglei666.com.demo03;
public class Test {
public static void main(String[] args) {
Dog wc = new Dog();
wc.printAge();
/* 輸出結果:
父age:10
子age:11
*/
}
}
1.4-super和this關鍵字(理解)
父類空間優先於子類物件的產生
在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。
目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。
程式碼體現在子類的構造方法呼叫時,一定先呼叫父類的構造方法。理解圖解如下:
super和this的含義:
- super :代表父類的儲存空間標識(可以理解為父親的引用)。
- this :代表當前物件的引用(誰呼叫就代表誰)。
super和this呼叫成員屬性和方法
this.成員變數 -- 本類的
super.成員變數 -- 父類的
this.成員方法名() -- 本類的
super.成員方法名() -- 父類的
super和this呼叫構造方法
this(...) -- 本類的構造方法
super(...) -- 父類的構造方法
子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。
手動呼叫父類構造會覆蓋預設的super()。
super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
1.5-子類可重寫父類方法(理解)
方法重寫Override
如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。
物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。程式碼如下:
父類:Fu
package www.penglei666.com.demo04;
public class Fu {
public void fn1(){
System.out.println("父類方法:fn");
}
}
子類:Zi
package www.penglei666.com.demo04;
public class Zi extends Fu {
@Override
public void fn2() {
System.out.println("Zi類中的方法:fn");
}
}
測試類:Test
package www.penglei666.com.demo04;
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.fn1(); // 輸出結果:父類方法:fn
zi.fn2(); // 輸出結果:Zi類中的方法:fn
}
}
如果子類父類中出現重名的成員方法,這時的訪問是一種特殊情況,叫做方法重寫 (Override)。
方法重寫 :子類中出現與父類一模一樣的方法時(返回值型別,方法名和引數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。宣告不變,重新實現
程式碼如下:
父類:Fu
package www.penglei666.com.demo04;
public class Fu {
public void fn(){
System.out.println("父類方法:fn");
}
}
子類:Zi
package www.penglei666.com.demo04;
public class Zi extends Fu {
@Override
public void fn() {
System.out.println("Zi類中的方法:fn");
}
}
測試類:
package www.penglei666.com.demo04;
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.fn(); // 輸出結果:Zi類中的方法:fn
}
}
方法重寫的應用
子類可以根據需要,定義特定於自己的行為。
既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴充套件增強。
比如新的手機增加來電顯示頭像的功能,程式碼如下:
父類:Phone
package www.penglei666.com.demo05;
/**
* 父類:Phone
*/
public class Phone {
public void sendMessage(){
System.out.println("傳送簡訊");
}
public void call(){
System.out.println("撥打電話");
}
public void showNum(){
System.out.println("來電顯示對方號碼");
}
}
子類:
package www.penglei666.com.demo05;
/**
* 子類:NewPhone
*/
public class NewPhone extends Phone {
//重寫父類的來電顯示號碼功能,並增加自己的顯示姓名和圖片功能
public void showNum(){
//呼叫父類已經存在的功能使用super
super.showNum();
//增加自己特有顯示姓名和圖片功能
System.out.println("來電顯示對方姓名");
System.out.println("來電顯示對方頭像");
}
}
測試類:Test
package www.penglei666.com.demo05;
public class Test {
public static void main(String[] args) {
NewPhone np = new NewPhone();
np.showNum();
/**
* 輸出結果:
* 來電顯示號碼
* 來電顯示對方姓名
* 來電顯示對方頭像
*/
}
}
這裡重寫時,用到super.父類成員方法,表示呼叫父類的成員方法。
1.6-子類的初始化過程(理解)
構造方法的作用是初始化成員變數的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中預設有一個super()
,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。程式碼如下:
父類:
package www.penglei666.com.demo06;
public class Fu {
public Fu(){
System.out.println("Fu類構造方法初始化");
}
}
子類:
package www.penglei666.com.demo06;
public class Zi extends Fu {
public Zi(){
// super(); 預設呼叫super
System.out.println("Zi類構造方法初始化");
}
}
測試類:
package www.penglei666.com.demo06;
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
/* 執行結果:
Fu類構造方法初始化
Zi類構造方法初始化
*/
}
}
1.7-繼承的特點(理解)
特點1:Java只支援單繼承,不支援多繼承。
//一個類只能有一個父類,不可以有多個父類。
class C extends A{} //ok
class C extends A,B... //error
特點2:Java支援多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}
第二章:抽象類
2.1-抽象類概述(瞭解)
抽象類的由來
當編寫一個類時,我們往往會為該類定義一些方法,這些方法是用來描述該類的功能具體實現方式,那麼這些方法都有具體的方法體。
分析事物時,發現了共性內容,就出現向上抽取(父類中定義)。
但是,可能會有這樣一種特殊情況,就是方法功能宣告相同,但方法功能主體不同。
那麼這時也可以抽取,但只抽取方法宣告,不抽取方法主體。那麼此方法就是一個抽象方法。
如:
- 描述狗的行為:吃
- 描述貓的行為:吃
- 描述牛的行為:吃
狗、貓、牛之間有共性,可以進行向上抽取父類定義共性。
抽取它們的所屬共性型別:動物。
由於狗、貓、牛都具有吃的功能,但是它們吃的食物不一樣(比如:開篇我們的需求狗、貓、牛都是吃貨,都有吃的行為,但是吃的食物第不同的,狗啃骨頭、貓吃魚、牛吃草)。
這時在描述動物類時,發現了有些功能不能夠具體描述,那麼,這些不具體的功能,需要在類中標識出來,通過java中的關鍵字abstract(抽象)修飾。當定義了抽象方法的類也必須被abstract關鍵字修飾,被abstract關鍵字修飾的類是抽象類。
總結
- 抽象方法 : 沒有方法體的方法。
- 抽象類:包含抽象方法的類。
2.2-抽象類、抽象方法的定義和使用(記憶)
抽象方法:
使用abstract
關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值型別 方法名 (引數列表);
示例程式碼:
public abstract void eat();
抽象類:
如果一個類包含抽象方法,那麼該類必須是抽象類。
定義格式:
public abstract class 類名字 {
}
示例程式碼:
public abstract class Animal {
public abstract void eat();
}
抽象類的使用:
繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須宣告為抽象類。最終,必須有子類實現該父類的抽象方法,否則,從最初的父類到最終的子類都不能建立物件,失去意義。
父類:Animal
package www.penglei666.com.demo07;
public abstract class Animal {
public abstract void eat();
}
子類:Dog
package www.penglei666.com.demo07;
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("我愛啃骨頭");
}
}
此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。
2.3-注意事項(瞭解)
- 抽象類不能建立物件,如果建立,編譯無法通過而報錯。只能建立其非抽象子類的物件。
- 理解方式:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
- 抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。
- 理解方式:子類的構造方法中,有預設的super(),需要訪問父類構造方法。
- 抽象類中,可以有成員變數。
- 理解方式:子類的共性的成員變數 , 可以定義在抽象父類中。
- 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
- 理解方式:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設計。
- 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報錯。除非該子類也是抽象類。
- 理解方式:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有意義。
第三章:綜合案例
案例需求:
某IT公司有多名員工,按照員工負責的工作不同,進行了部門的劃分(研發部員工、維護部員工)。研發部根據所需研發的內容不同,又分為JavaEE工程師、Android工程師;維護部根據所需維護的內容不同,又分為網路維護工程師、硬體維護工程師。
公司的每名員工都有他們自己的員工編號、姓名,並要做它們所負責的工作。
工作內容:
-
JavaEE工程師: 員工號為xxx的 xxx員工,正在研發淘寶網站
-
Android工程師:員工號為xxx的 xxx員工,正在研發淘寶手機客戶端軟體
-
網路維護工程師:員工號為xxx的 xxx員工,正在檢查網路是否暢通
-
硬體維護工程師:員工號為xxx的 xxx員工,正在修復印表機
請根據描述,完成員工體系中所有類的定義,並指定類之間的繼承關係。進行XX工程師類的物件建立,完成工作方法的呼叫。
案例分析:
根據上述部門的描述,得出如下的員工體系圖
根據員工資訊的描述,確定每個員工都有員工編號、姓名、要進行工作。則把這些共同的屬性與功能抽取到父類中(員工類),關於工作的內容由具體的工程師來進行指定。
工作內容:
- JavaEE工程師:員工號為xxx的 xxx員工,正在研發淘寶網站
- Android工程師:員工號為xxx的 xxx員工,正在研發淘寶手機客戶端軟體
- 網路維護工程師:員工號為xxx的 xxx員工,正在檢查網路是否暢通
- 硬體維護工程師:員工號為xxx的 xxx員工,正在修復印表機
建立JavaEE工程師物件,完成工作方法的呼叫。
實現程式碼:
員工類:Employee
public abstract class Employee {
private String id;// 員工編號
private String name; // 員工姓名
public String getId() {
returnid;
}
publicvoid setId(String id) {
this.id = id;
}
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
//工作方法(抽象方法)
public abstract void work();
}
定義研發部員工類Developer 繼承 員工類Employee
public abstract class Developer extends Employee {
}
定義維護部員工類Maintainer 繼承 員工類Employee
public abstract class Maintainer extends Employee {
}
定義JavaEE工程師 繼承 研發部員工類,重寫工作方法
public class JavaEE extends Developer {
@Override
public void work() {
System.out.println("員工號為 " + getId() + " 的 " + getName() + " 員工,正在研發淘寶網站");
}
}
定義Android工程師 繼承 研發部員工類,重寫工作方法
public class Android extends Developer {
@Override
public void work() {
System.out.println("員工號為 " + getId() + " 的 " + getName() + " 員工,正在研發淘寶手機客戶端軟體");
}
}
定義Network網路維護工程師 繼承 維護部員工類,重寫工作方法
public class Network extends Maintainer {
@Override
public void work() {
System.out.println("員工號為 " + getId() + " 的 " + getName() + " 員工,正在檢查網路是否暢通");
}
}
定義Hardware硬體維護工程師 繼承 維護部員工類,重寫工作方法
public class Hardware extends Maintainer {
@Override
public void work() {
System.out.println("員工號為 " + getId() + " 的 " + getName() + " 員工,正在修復印表機");
}
}
在測試類中,建立JavaEE工程師物件,完成工作方法的呼叫
public class Test {
public static void main(String[] args) {
//建立JavaEE工程師員工物件
JavaEE ee = new JavaEE();
//設定該員工的編號
ee.setId("000015");
//設定該員工的姓名
ee.setName("小明");
//呼叫該員工的工作方法
ee.work();
}
}