1. 程式人生 > 其它 >P09_繼承-super-this-抽象類

P09_繼承-super-this-抽象類

第一章 繼承

1.1 概述

由來

​ 多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那一個類即可。

​ 其中,多個類可以稱為子類,單獨那一個類稱為父類、超類(superclass)或者基類。

​ 繼承描述的是事物之間的所屬關係,這種關係是: is-a 的關係。父類更通用,子類更具體。我們通過繼承,可以使多種事物之間形成一種關係體系。

定義

  • 繼承:就是子類繼承父類的屬性和行為,使得子類物件具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。

好處

  1. 提高程式碼的複用性。
  2. 類與類之間產生了關係,是多型的前提。

1.2 繼承的格式

通過 extends 關鍵字,可以宣告一個子類繼承另外一個父類,定義格式如下:

/*
* 定義員工類Employee,做為父類
*/
class Employee {
    String name; // 定義name屬性
    // 定義員工的工作方法
    public void work() {
    	System.out.println("盡心盡力地工作");
    }
}
/*
* 定義講師類Teacher 繼承 員工類Employee
*/
class Teacher extends Employee {
    // 定義一個列印name的方法
    public void printName() {
    	System.out.println("name=" + name);
    }
}
/*
* 定義測試類
*/
public class ExtendDemo01 {
    public static void main(String[] args) {
        // 建立一個講師類物件
        Teacher t = new Teacher();
        // 為該員工類的name屬性進行賦值
        t.name = "小明";
        // 呼叫該員工的printName()方法
        t.printName(); // name = 小明
        // 呼叫Teacher類繼承來的work()方法
        t.work(); // 盡心盡力地工作
    }
}

1.3 繼承後的特點——成員變數

​ 當類之間產生了關係後,其中各類中的成員變數,又產生了哪些影響呢?

成員變數不重名

如果子類父類中出現不重名的成員變數,這時的訪問是沒有影響的。程式碼如下:

class Fu {
    // Fu中的成員變數。
    int num = 5;
}
class Zi extends Fu {
    // Zi中的成員變數
    int num2 = 6;
    // Zi中的成員方法
    public void show() {
        // 訪問父類中的num,
        System.out.println("Fu num="+num); // 繼承而來,所以直接訪問。
        // 訪問子類中的num2
        System.out.println("Zi num2="+num2);
    }
}
class ExtendDemo02 {
    public static void main(String[] args) {
        // 建立子類物件
        Zi z = new Zi();
        // 呼叫子類中的show方法
        z.show();
    }
}
演示結果:
Fu num = 5
Zi num2 = 6

成員變數重名

​ 如果子類父類中出現重名的成員變數,這時的訪問是有影響的。程式碼如下:

class Fu {
    // Fu中的成員變數。
    int num = 5;
}
class Zi extends Fu {
    // Zi中的成員變數
    int num = 6;
    public void show() {
        // 訪問父類中的num
        System.out.println("Fu num=" + num);
        // 訪問子類中的num
        System.out.println("Zi num=" + num);
    }
}
class ExtendsDemo03 {
    public static void main(String[] args) {
        // 建立子類物件
        Zi z = new Zi();
        // 呼叫子類中的show方法
        z.show();
    }
}
演示結果:
Fu num = 6
Zi num = 6

​ 子父類中出現了同名的成員變數時,在子類中需要訪問父類中非私有成員變數時,需要使用super 關鍵字,修飾父類成員變數,類似於之前學過的 this 。

使用格式:

super.父類成員變數名

子類方法需要修改,程式碼如下:

class Zi extends Fu {
    // Zi中的成員變數
    int num = 6;
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num=" + super.num);
        //訪問子類中的num
        System.out.println("Zi num=" + this.num);
    }
}
演示結果:
Fu num = 5
Zi num = 6

​ Fu 類中的成員變數是非私有的,子類中可以直接訪問。若Fu 類中的成員變數私有了,子類是不能
直接訪問的。通常編碼時,我們遵循封裝的原則,使用private修飾成員變數,那麼如何訪問父類的私有成員
變數呢?對!可以在父類中提供公共的getXxx方法和setXxx方法。

1.4 繼承後的特點——成員方法

​ 當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢?

成員方法不重名

​ 如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。程式碼如下:

class Fu{
    public void show(){
    	System.out.println("Fu類中的show方法執行");
    }
}
class Zi extends Fu{
    public void show2(){
    	System.out.println("Zi類中的show2方法執行");
    }
}
public class ExtendsDemo04{
    public static void main(String[] args) {
        Zi z = new Zi();
        //子類中沒有show方法,但是可以找到父類方法去執行
        z.show();
        z.show2();
    }
}

成員方法重名——重寫(Override)

​ 如果子類父類中出現重名的成員方法,這時的訪問是一種特殊情況,叫做方法重寫 (Override)。

  • 方法重寫 :子類中出現與父類一模一樣的方法時(返回值型別,方法名和引數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。宣告不變,重新實現。

程式碼如下:

class Fu {
    public void show() {
    	System.out.println("Fu show");
    }
}
class Zi extends Fu {
    //子類重寫了父類的show方法
    public void show() {
    	System.out.println("Zi show");
    }
}
public class ExtendsDemo05{
    public static void main(String[] args) {
        Zi z = new Zi();
        // 子類中有show方法,只執行重寫後的show方法
        z.show(); // Zi show
    }
}

重寫的應用

​ 子類可以根據需要,定義特定於自己的行為。既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴充套件增強。比如新的手機增加來電顯示頭像的功能,程式碼如下:

class Phone {
    public void sendMessage(){
    	System.out.println("發簡訊");
    }
    public void call(){
    	System.out.println("打電話");
    }
    public void showNum(){
    	System.out.println("來電顯示號碼");
    }
}
    //智慧手機類
class NewPhone extends Phone {
    //重寫父類的來電顯示號碼功能,並增加自己的顯示姓名和圖片功能
    public void showNum(){
        //呼叫父類已經存在的功能使用super
        super.showNum();
        //增加自己特有顯示姓名和圖片功能
        System.out.println("顯示來電姓名");
        System.out.println("顯示頭像");
    }													
}
public class ExtendsDemo06 {
    public static void main(String[] args) {
        // 建立子類物件
        NewPhone np = new NewPhone();
        // 呼叫父類繼承而來的方法
        np.call();
        // 呼叫子類重寫的方法
        np.showNum();
    }
}

​ 這裡重寫時,用到super.父類成員方法,表示呼叫父類的成員方法。

注意事項

  1. 子類方法覆蓋父類方法,必須要保證許可權大於等於父類許可權。
  2. 子類方法覆蓋父類方法,返回值型別、函式名和引數列表都要一模一樣。

1.5 繼承後的特點——構造方法

當類之間產生了關係,其中各類中的構造方法,又產生了哪些影響呢?

首先我們要回憶兩個事情,構造方法的定義格式和作用。

  1. 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。
  2. 構造方法的作用是初始化成員變數的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構
    造方法中預設有一個super() ,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。代
    碼如下:
class Fu {
    private int n;
    Fu(){
    	System.out.println("Fu()");
    }
}
class Zi extends Fu {
    Zi(){
        // super(),呼叫父類構造方法
        super();
        System.out.println("Zi()");
    }
}
public class ExtendsDemo07{
    public static void main (String args[]){
    	Zi zi = new Zi();
    }
}
輸出結果:
Fu()
Zi()

1.6 super和this

父類空間優先於子類物件產生

​ 在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。程式碼體現在子類的構造方法呼叫時,一定先呼叫父類的構造方法。理解圖解如下:

super和this的含義

  • super :代表父類的儲存空間標識(可以理解為父親的引用)。
  • this :代表當前物件的引用(誰呼叫就代表誰)。

super和this的用法

  1. 訪問成員
this.成員變數 ‐‐ 本類的
super.成員變數 ‐‐ 父類的
this.成員方法名() ‐‐ 本類的
super.成員方法名() ‐‐ 父類的

用法演示,程式碼如下:

class Animal {
    public void eat() {
    	System.out.println("animal : eat");
    }
}
class Cat extends Animal {
    public void eat() {
    	System.out.println("cat : eat");
    }
    public void eatTest() {
        this.eat(); // this 呼叫本類的方法
        super.eat(); // super 呼叫父類的方法
    }
}
public class ExtendsDemo08 {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        Cat c = new Cat();
        c.eatTest();
    }
}
輸出結果為:
animal : eat
cat : eat
animal : eat
  1. 訪問構造方法
this(...) ‐‐ 本類的構造方法
super(...) ‐‐ 父類的構造方法

​ 子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。

1.7 繼承的特點

  1. Java只支援單繼承,不支援多繼承。
//一個類只能有一個父類,不可以有多個父類。
class C extends A{} //ok
class C extends A,B... //error
  1. Java支援多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}

頂層父類是Object類。所有的類預設繼承Object,作為父類。

  1. 子類和父類是一種相對的概念。

第二章 抽象類

2.1 概述

由來

​ 父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法宣告和方法主體,只有宣告還有意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類。

定義

  • 抽象方法 : 沒有方法體的方法。
  • 抽象類:包含抽象方法的類。

2.2 abstract使用格式

抽象方法

​ 使用abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。

定義格式:

修飾符 abstract 返回值型別 方法名 (引數列表);

程式碼舉例:

public abstract void run();

抽象類

​ 如果一個類包含抽象方法,那麼該類必須是抽象類。

定義格式:

abstract class 類名字 {
}

程式碼舉例:

public abstract class Animal {
	public abstract void run();
}

抽象的使用

​ 繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須宣告為抽象類。最終,必須有子類實現該父類的抽象方法,否則,從最初的父類到最終的子類都不能建立物件,失去意義。

程式碼舉例:

public class Cat extends Animal {
    public void run (){
    	System.out.println("小貓在牆頭走~~~");
    }
}

public class CatTest {
    public static void main(String[] args) {
    // 建立子類物件
    Cat c = new Cat();
    // 呼叫run方法
    c.run();
    }
}
輸出結果:
小貓在牆頭走~~~

​ 此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。

2.3 注意事項

關於抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。

  1. 抽象類不能建立物件,如果建立,編譯無法通過而報錯。只能建立其非抽象子類的物件。
  • 理解:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
  1. 抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。
  • 理解:子類的構造方法中,有預設的super(),需要訪問父類構造方法。
  1. 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
  • 理解:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設
    計。
  1. 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報錯。除非該子類也是抽象
    類。
  • 理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有
    意義。

第三章 繼承的綜合案例

3.1 綜合案例:群主發普通紅包

群主發普通紅包。某群有多名成員,群主給成員發普通紅包。普通紅包的規則:

  1. 群主的一筆金額,從群主餘額中扣除,平均分成n等份,讓成員領取。
  2. 成員領取紅包後,儲存到成員餘額中。

請根據描述,完成案例中所有類的定義以及指定類之間的繼承關係,並完成發紅包的操作。

3.2 案例分析

根據描述分析,得出如下繼承體系:

  • 群主,發紅包 - 繼承 -> User 使用者名稱 餘額 展示資訊()
  • 成員,開紅包 - 繼承 -> User 使用者名稱 餘額 展示資訊()

3.3 案例實現

定義使用者類:

public class User {
    // 成員變數
    private String username; // 使用者名稱
    private double leftMoney; // 餘額
    // 構造方法
    public User() { }
    public User(String username, double leftMoney) {
        this.username = username;
        this.leftMoney = leftMoney;
    }
    // get/set方法
    public String getUsername() {
    	return username;
    }
    public void setUsername(String username) {
    	this.username = username;
    }
    public double getLeftMoney() {
    	return leftMoney;
    }
    public void setLeftMoney(double leftMoney) {
    	this.leftMoney = leftMoney;
    }
    // 展示資訊的方法
    public void show() {
    	System.out.println("使用者名稱:"+ username +" , 餘額為:" + leftMoney + "元");
    }
}

定義群主類:

public class QunZhu extends User {
    // 新增構造方法
    public QunZhu() {
    }
    public QunZhu(String username, double leftMoney) {
        // 通過super 呼叫父類構造方法
        super(username, leftMoney);
    }
    /*
    群主發紅包,就是把一個整數的金額,分層若干等份。
    1.獲取群主餘額,是否夠發紅包.
    不能則返回null,並提示.
    能則繼續.
    2.修改群主餘額.
    3.拆分紅包.
    3.1.如果能整除,那麼就平均分。
    3.2.如果不能整除,那麼就把餘數分給最後一份。
    */
    public ArrayList<Double> send(int money, int count) {
        // 獲取群主餘額
        double leftMoney = getLeftMoney();
        if(money > leftMoney) {
        	return null;
        }
        // 修改群主餘額的
        setLeftMoney(leftMoney ‐ money);
        // 建立一個集合,儲存等份金額
        ArrayList<Double> list = new ArrayList<>();
        // 擴大100倍,相當於折算成'分'為單位,避免小數運算損失精度的問題
        money = money * 100;
        // 每份的金額
        int m = money / count;
        // 不能整除的餘數
        int l = money % count;
        // 無論是否整除,n‐1份,都是每份的等額金額
        for (int i = 0; i < count ‐ 1; i++) {
            // 縮小100倍,折算成 '元'
            list.add(m / 100.0);
        }
        // 判斷是否整除
        if (l == 0) {
            // 能整除, 最後一份金額,與之前每份金額一致
            list.add(m / 100.0);
        } else {
            // 不能整除, 最後一份的金額,是之前每份金額+餘數金額
            list.add((m + l) / 100.00);
        }
        // 返回集合
        return list;
    }
}

定義成員類:

public class Member extends User {
    public Member() {
    }
    public Member(String username, double leftMoney) {
    	super(username, leftMoney);
    }
    // 開啟紅包,就是從集合中,隨機取出一份,儲存到自己的餘額中
    public void openHongbao(ArrayList<Double> list) {
        // 建立Random物件
        Random r = new Random();
        // 隨機生成一個角標
        int index = r.nextInt(list.size());
        // 移除一個金額
        Double money = list.remove(index);
        // 直接呼叫父類方法,設定到餘額
        setLeftMoney( money );
	}
}

定義測試類:

public class Test {
    public static void main(String[] args) {
        // 建立一個群主物件
        QunZhu qz = new QunZhu("群主" , 200);
        // 建立一個鍵盤錄入
        Scanner sc = new Scanner();
        System.out.println("請輸入金額:");
        int money = sc.nextInt();
        System.out.println("請輸入個數:");
        int count = sc.nextInt();
        // 傳送紅包
        ArrayList<Double> sendList = s.send(money,count);
        // 判斷,如果餘額不足
        if(sendList == null){
        System.out.println(" 餘額不足...");
        return;
    }
    // 建立三個成員
    Member m = new Member();
    Member m2 = new Member();
    Member m3 = new Member();
    // 開啟紅包
    m.openHongbao(sendList);
    m2.openHongbao(sendList);
    m3.openHongbao(sendList);
    // 展示資訊
    qz.show();
    m.show();
    m2.show();
    m3.show();
    }
}