# 繼承的使用

如果有一種情況,定義了 Aminal 類別,它的定義有:

屬性:
年齡、重量、名字
方法:
移動

若此時,我又定義了一個類別,稱作 Dog :

屬性:
年齡、重量、名字、毛色、身長
方法:
移動、睡、吃、喝

這時,我們會發現 Dog 有許多部分與 Animal 重複。
我們可以在定義時,就延伸動物的類別,再加上狗專屬的特性即可。
Dog 就繼承了 AnimalAnimal 在這裡是父類別, Dog 就成了繼承父類別的子類別。

# 程式碼呈現 extends

class 子類別 extends 父類別{
    // 程式碼
}

只要在子類別的後方加上 extends (延伸) 關鍵字就可以全擁有父類別的所有成員了。
如果用上述的例子還原程式碼:

程式碼還原
class Animal{
    int age;
    int weight;
    String name;
    int move(){
    }
}
class Dog extends Animal{
    Color hair;
    void sleep(){
    }
    void eat(){
    }
    void drink(){
    }
}

我們可以將繼承想成是一般化與特殊化的關係,
繼承樹上越頂端的父類別擁有越一般的特性,越底端的子類別越特殊
子類別擁有父類別定義的所有成員,再多了自己特有的東西。

Java 是純物件導向的程式語言,
每個類別,包括自訂的類別,都繼承 Object。

# this 關鍵字

指到自己,也就是自己類別的成員,也可以理解為:指向對象的一個指針。

class People{
    String name;
    int age;
    People(String name){
        this.name = name;
    }
    String getName(){
        return this.name;
    }
}

this.name 意思是『自己這個類別的成員 name』,繼承關係越複雜的情況下這樣寫法可以大大增加程式的可讀性。

# 自己的建構字 this (.)

如果寫了很多建構子提供多元的建構物件方式,建構子之間彼此可以互相呼叫:

class Human{
    String name;
    int age;
    static int totalCount = 0;
    Human(){
        name = "untitled";
        age = -1;  // 使用 - 1 來標記沒有被設定,否則會初始化為 0,但人類有可能 0 歲
        totalCount++;
    }
    Human(String name){  
        this();                          
        this.name = name;
    }
    Human(String name,int age){
        this(name);  
        this.age = age;
    }
    void printInfo(){
        System.out.println(name+" 年齡:"+age+" 目前總人數:"+totalCount);
    }
}

this() 表示呼叫自己不帶參數的建構子, this(String) 表示呼叫自己帶有一個字串參數的建構子,以此類推。

this() 須放置於第一行,否則,初始化的部分會出錯!!

測試程式
class Test{
    public static void main(String[] args){
        Human h1 = new Human();
        h1.printInfo();
        Human h2 = new Human("Zrn");
        h2.printInfo();
        Human h3 = new Human("Zen",17);
        h3.printInfo();
    }
}
執行結果
untitled 年齡:-1 目前總人數:1
Zrn 年齡:-1 目前總人數:2
Zen 年齡:18 目前總人數:3

# super 關鍵字

指到父類別,使用方法跟 this 類似。

Animal.class
class Animal {
    int height;
    int weight;
    static int totalCount = 0;
    Animal() {
        this(-1, -1);
    }
    Animal(int h) {
        this(h, -1);
    }
    Animal(int h, int w) {
        this.height = h;
        this.weight = w;
        totalCount++;
    }
    String getInfo() {
        return "身長:" + height + " 重量:" + weight;
    }
}
Dog.class
class Dog extends Animal {
    String color;
    static int totalCount = 0;
    Dog() {
        this(-1, -1, "noset");
    }
    Dog(int h, int w) {
        this(h, w, "noset");
    }
    Dog(String c) {
        this(-1, -1, c);
    }
    Dog(int h, int w, String c) {
        super(h, w);
        this.color = c;
        totalCount++;
    }
    String getInfo() {
        return super.getInfo() + " 毛色:" + this.color;
    }
}

# 父類別的建構子 super (.)

很多時候父類別已經定義好的東西,子類別直接用就好,設計上比較好維護,設計邏輯比較有階層性。

# 覆寫

對照上面的程式碼,依據繼承原則,我們可以知道 Dog 繼承了 Animal 。所以,不需要多寫,但是父類別又太過於一般化,沒辦法滿足子類別需要的功能,所以子類別『 覆寫(override) 』了父類別的方法,創造了特殊且適合自己的方法。

class Test {
    public static void main(String[] args) {
        Dog d1 = new Dog();
        System.out.println(d1.getInfo());
        Dog d2 = new Dog(30, 10);
        System.out.println(d2.getInfo());
        Dog d3 = new Dog("white");
        System.out.println(d3.getInfo());
        Dog d4 = new Dog(30, 10, "white");
        System.out.println(d4.getInfo());
        System.out.println("動物數量:" + Animal.totalCount);
        System.out.println("狗狗數量:" + Dog.totalCount);
    }// end of main(String[])
}
執行結果:
身長:-1 重量:-1 毛色:noset
身長:30 重量:10 毛色:noset
身長:-1 重量:-1 毛色:white
身長:30 重量:10 毛色:white
動物數量:4
狗狗數量:4

# 覆寫的存取修飾限制

覆寫 Override ,字面上的意思就是『覆蓋重寫』。
在繼承中關係,父類別定義了一些方法,子類別覺得不適用的話可以『覆蓋』掉父類別的方法,然後『重寫』屬於自己的方法。

舉例
class A{
    void printInfo(){
        System.out.println("hello, I am A.");
    }
}
class B extends A{
    void printInfo(){
        System.out.println("hello, I am B.");
    }
}
class C extends A{
}
測試
class Test {
    public static void main(String[] args) {
        B b = new B();
        b.printInfo();
        C c = new C();
        c.printInfo();
    }
}
執行結果
hello, I am B.
hello, I am A.

上述程式中,B 與 C 都是繼承 A,表示擁有了 A 所有的成員,但 B 覆寫了 printInfo () 方法,而 C 沒有。 所以在呼叫的時候,物件 b 會使用 B 類別覆寫的方法,而物件 c 因為 C 類別沒有自己定義,所以會使用到父類別 A 所定義的 printInfo ()。

要覆寫父類別方法必須滿足幾個條件:

  1. 父類別方法不能用 final 修飾。
  2. 子類別覆寫的方法名稱、回傳型態、參數個數順序需相同。
  3. 子類別覆寫的方法,其開放權限不可以小於要覆寫的父類別方法。
    存取修飾子的開放權限從大到小: public -> protected -> (no modifier) -> private
第三點範例
class A{
    // 注意存取修飾子是 (no modifier)
    void printInfo(){
        System.out.println("hello, this is A.");
    }
}
class B extends A{
    // ↓ 編譯錯誤,覆寫的方法存取權限小於覆寫對象
    private void printInfo(){
        System.out.println("hello, this is B.");
    }
}

在 A 類別中的 printInfo() 方法修飾子是 (no modifier) ,依據覆寫的開放權限規則,
B 類別繼承了 A 類別想覆寫 printInfo () ,覆寫的開放權限必須為 publicprotected(no modifier) ,重點就是不能小於覆寫對象,否則會發生編譯錯誤: 『Cannot reduce the visibility of the inherited method from A』

更新於 閱讀次數

用實際行動犒賞爆肝的我😀

Zrn Ye LinePay

LinePay