# 繼承

> 介紹繼承的概念及程式的撰寫方式。

## 為什麼需要繼承？ Why Inheritance?

試想一種情況：

有一個Aminal類別，它的定義如下：

```java
屬性：
重量、身長、年齡。

方法：
移動。
```

好，這個類別定義好，現在要再定義一個Dog的類別：

```java
屬性：
重量、身長、年齡、毛色。

方法：
移動、吃、睡、吠叫。
```

有沒有發現到狗的定義中，許多東西都跟動物重複。

在現實中，狗是一種動物，應該擁有動物的屬性及方法，然後再加上狗專屬的屬性和方法。

以這個例子來說，我們可以把動物當成父類別(或稱超類別super class)，狗『繼承』動物，狗是『子類別 subclass』。

**子類別會擁有父類別的所有屬性、方法，再加上自己定義的屬性及方法，所以可以說子類別是父類別的延伸(extend)。** (這句話超重要！多個看幾十次，想一想)

類別圖：

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2Ff7c516626ee0a2329058521b759df7c747270d4e.jpg?generation=1604653627110027\&alt=media)

這是UML (統一建模語言，Unified Modeling Language) 的類別圖(簡易版)，常用來描述類別之間的關係。 實心箭頭表示繼承關係，由子類別指向父類別。

圖中讀作 Dog 繼承 Animal。 另外一種常見的說法是： Dog is a Animal.

繼承的概念用『is a』來表述。 反過來說 Animal is a Dog.是不成立的，利用『is a』可以幫助思考。

## Java程式要如何表示繼承關係呢？ 利用關鍵字 extends：

```java
class 子類別 extends 父類別{
    // code
}
```

沒錯，就是延伸(extends)，父類別定義的東西，子類別只要繼承就等於全部擁有了，然後以父類別擁有的成員為基本，再延伸出自己特有的東西。

以上面假設的 Animal 與 Dog 類別來看，程式會長這樣：

```java
class Animal{
    int height;
    int weight;
    int age;
    void move(){
    } 
} // end of class Animal

class Dog extends Animal{
    Color hair;
    void eat(){
    }
    void sleep(){
    }
    void bark(){
    }
} // end of class Dog
```

有沒有稍微體會到繼承的方便性呢？

## 一般化、特殊化

我們可以將繼承想成是一般化（Generalization）與特殊化（Specialization）的關係，繼承樹上越頂端的父類別擁有越『一般』的特性，越底端的子類別越『特殊』。

子類別擁有父類別定義的所有成員，再多了自己特有的東西。

看起來就像這樣：

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2Fe6ab8ac651644a72dbbb305012c10df3453ea6f1.jpg?generation=1604653628098135\&alt=media)

父類別的功能少，子類別的功能多，不要因為父類別的英文是(super)就覺得比較厲害。

## 萬物之父 Object

我們知道Java是純物件導向的程式語言，而每個類別，包括自訂的類別，都繼承Object。

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2F0f5ef9adfbfb32bc1423b6647ca835a0d1c6a56b.jpg?generation=1604653627501308\&alt=media)

特別要提的是Java只支援『單向繼承』，也就是說一個子類別只可以有一個父類別，不過一個父類別可以被多個子類別繼承。

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2Fe5a00d53106ba0826d57b1253d83d726a66a8d6a.jpg?generation=1604653627378051\&alt=media)

定義類別的時候，如果沒有使用關鍵字extends，Java會自行`extends Object`：

```java
class Animal{
    // code
}
// 等價於下面敘述，Java會自動幫你extends Object
class Animal extends Object{
    // code
}
```

## 關鍵字 this 、 super

this跟super都是關鍵字，都是reference。 指到哪裡呢？

### this

指到自己，也就是自己類別的成員。

```java
class Human{
    String name;
    int age;
    Human(String str){
        this.name = str;
    }
    String getName(){
        return this.name;
    }
}
```

上述程式中， `this.name`意思是『自己這個類別的成員name』，當然在這個情況不寫也無所謂，但繼承關係越複雜的情況下這樣寫法可以大大增加程式的可讀性。

### 自己的建構字 this(.);

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

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

上述程式中，`this()`表示呼叫自己不帶參數的建構子，`this(String)`表示呼叫自己帶有一個字串參數的建構子，以此類推。

這樣寫的好處是，各建構子之間有功能擴充的效果，已經寫好的程式可以被充分的再利用，要修改某個環節也比較不會出錯。

**特別要注意的是：**

`this(.)` 建構子只能放在第一行！！！

**『Constructor call must be the first statement in a constructor.』**

```java
Human(String str){  // 如果這樣寫會編譯錯誤，底下兩行位置需要互換
    this.name = str;
    this();        // 因為要讓建構子跑完，初始化好東西，才能做後續的設定
}
```

好，那用定義好的3個建構子來測試一下程式：

```java
class Test{
    public static void main(String[] args){
        Human h1 = new Human();
        h1.printInfo();
        Human h2 = new Human("小木");
        h2.printInfo();
        Human h3 = new Human("小婷",18);
        h3.printInfo();
    }// end of main(String[])
}
```

執行結果：

```java
untitled 年齡：-1 目前總人數：1
小木 年齡：-1 目前總人數：2
小婷 年齡：18 目前總人數：3
```

### super

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

```java
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;
    }
} // end of class Animal

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;
    }
} // end of class Dog
```

程式有點長，慢慢看沒關係。

Animal中帶有兩個參數的建構子 `Animal(int h,int w)` 視為主要負責初始化功能的運算子，其他參數比較少的建構子就負責呼叫這個建構子。

Dog的建構子中，主要運做的是 `Dog(int h,int w,String c)`，其他參數比較少的運算子只需要設計應該帶什麼參數給他。 這種被稱為方法的包裝(wrapped) 是常見且比較容易設計的做法。

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

利用super(.)可以呼叫父類別中定義好相應參數的建構子，那為什麼還要特地呼叫父類別的建構子呢？ 很多時候父類別已經定義好的東西，子類別直接用就好，設計上比較好維護，設計邏輯比較有階層性。

### 覆寫 Override

再來看getInfo()方法，Animal裡面已經定義了一個，依據繼承的理論，Dog繼承Animal應該不用自己寫也會有一個getInfo()才對。 沒錯，Dog如果不自己定義會有一個跟Animal『一模一樣』的getInfo()方法，但很明顯，父類別太過於一般化，沒辦法滿足子類別需要的功能(以此為例就是資訊量不夠)，所以子類&#x5225;**『覆寫(override)』**&#x4E86;父類別的方法，創造了特殊且適合自己的getInfo()。

super.方法()、super.欄位，就是呼叫父類別那邊的方法、欄位，當然前提是存取修飾子允許你看到。

測試一下執行結果：

```java
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[])
}// end of class Test
```

執行結果：

```java
身長：-1 重量：-1 毛色：noset
身長：30 重量：10 毛色：noset
身長：-1 重量：-1 毛色：white
身長：30 重量：10 毛色：white
動物數量：4
狗狗數量：4
```

## 層層初始化

我們知道子類別擁有父類別的所有程式碼，那初始化順序呢？

初始化子類別前，還要先初始化父類別，畢竟父類別建構出來才有足夠的基礎資料去建構『延伸』的部份，所以每個類別要建構的時候，都會往上追朔，追朔到 Object 開始一層一層建構下來，也是因為這樣，子類別才能擁有父類別的所有成員。

舉個例子：

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2F506064f3a7e50abbb5758788c6c145dc05a30b15.jpg?generation=1604653627889987\&alt=media)

```java
class A{
    A(){
        System.out.println("這裡是A的建構子");
    }
}
class B extends A{
    B(){
        System.out.println("這裡是B的建構子");
    }
}
class C extends B{
    C(){
        System.out.println("這裡是C的建構子");
    }
}
```

建構一個C物件試試：

```java
class Test {
    public static void main(String[] args) {
        C c = new C();
    }// end of main(String[])
}// end of class Test
```

執行結果：

```java
這裡是A的建構子
這裡是B的建構子
這裡是C的建構子
```

哇塞，真是太神奇了！

看一下類別示意圖：

![](https://1201963393-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MLRnhT9aTkN9HvaBP28%2Fsync%2Fc008c87eca9cf174a436d668cf37e5ff665726e1.jpg?generation=1604653627858986\&alt=media)

我們要建構的是C，而C是B的延伸，所以要先有B，而B是A的延伸，所以要先有A，而A是Object的延伸，所以要先有Object。 於是就從最頂端的父類別一直建構下來。

好，現在我知道需要從父類別初始化下來，但建構子呢？ 一個類別可以定義無數個建構子，他怎麼知道我要用哪個建構子來建構我的物件？ 到底是以什麼機制來建構父類別的？

嗯，回想一下，當初在定義類別的時候，如果沒有定義任何建構子，Java會幫你定義一個不帶參數不做任何事的建構子，現在同樣的老招又來一次！

只要你的建構子中『沒有呼叫任何建構子』，就會在『第一行』偷偷幫你家上去一個 `super();` 有多偷偷呢？ 你連看都看不到！！ 但他就是存在於最後的程式碼中。

以上的程式來說，就像這樣：

```java
class A{
    A(){
        super();  // 這行不寫的話，Java會幫你加上，但你看不到
        System.out.println("這裡是A的建構子");
    }
}
class B extends A{
    B(){
        super();  // 這行不寫的話，Java會幫你加上，但你看不到
        System.out.println("這裡是B的建構子");
    }
}
class C extends B{
    C(){
        super();  // 這行不寫的話，Java會幫你加上，但你看不到
        System.out.println("這裡是C的建構子");
    }
}
```

好的，現在知道他會自動幫我呼叫`super();`來建構父類別，但是如果我不想用這個不帶參數的建構子呢？ 我辛苦設計那麼多建構子，他只會幫我呼叫不帶參數的，太慘了吧！

嗯嗯，沒錯就是這麼慘，所以如果要呼叫有帶參數的`super(.);`你就要自己寫！

觀察底下程式，想想執行結果：

```java
class A{
    A(){
        System.out.println("這裡是A的建構子");
    }
}
class B extends A{
    B(){
        System.out.println("這裡是B的建構子");
    }
    B(String str){
        this();
        System.out.println("嗨這裡是B："+str);
    }
}
class C extends B{
    C(){
        this("hello tina");
        System.out.println("這裡是C的建構子");
    }
    C(String str){
        super(str);
        System.out.println("嗨這裡是C："+str);
    }
}
```

主程式：

```java
class Test {
    public static void main(String[] args) {
        C c = new C();
    }// end of main(String[])
}// end of class Test
```

執行結果：

```java
這裡是A的建構子
這裡是B的建構子
嗨這裡是B：hello tina
嗨這裡是C：hello tina
這裡是C的建構子
```

如果跟你想的不一樣，在重新看一下上面的描述再想想，哪理卡卡的可以問我。 這裡是重要的繼承觀念。

## 存取修飾子 protected

在[存取修飾子](https://yubin551.gitbooks.io/java-note/content/AccessModifier.html)的章節提過，現在剛好提到繼承再拿出來討論。

protected是個關鍵字，開放的最大權限為『不同套件的子類別』可以存取。

假設 Animal 與 Dog 位在不同package，先看Animal的程式碼：

```java
 package A;
 public class Animal {
     public String name;  // 4個屬性剛好4種權限範圍都做測試
     protected int height;
     int weight;
     private int age;
     // ↓這個修飾子一定要public或protected，不然不同類別的Dog不能用他來建構物件
     public Animal(String str,int h,int w,int a){
         this.name = str;
         this.height = h;
         this.weight = w;
         this.age = a;
     }
 }
```

再看Dog的程式碼：

```java
 package B;
 import A.Aminal;
 public class Dog extends Animal{
     String color;
     public Dog(String str,int h,int w,int a,String c){
         super(str,h,w,a);
         this.color = c;
     }
     public void printInfo(){
         System.out.println(name);    // OK, public 不同套件也可以存取
         System.out.println(height);  // OK, protected 允許不同套件子類別存取
         System.out.println(weight);  // 編譯錯誤，預設只有同 package 可以存取
         System.out.println(age);     // 編譯錯誤， private 只有自身類別能存取
         System.out.println(color);   // OK, 自己類別定義的成員當然OK
     }
 }
```

## 覆寫的存取修飾限制

> 上面的範例程式有稍為提到過覆寫(override)，這邊再詳細討論一下，以及一些限制。

### 覆寫 Override，字面上的意思就是『覆蓋重寫』。

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

舉個例子：

```java
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{
}
```

測試程式：

```java
class Test {
    public static void main(String[] args) {
        B b = new B();
        b.printInfo();
        C c = new C();
        c.printInfo();
    }// end of main(String[])
}// end of class Test
```

執行結果：

```java
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. 子類別覆寫的方法，其開放權限不可以小於要覆寫的父類別方法。

#### 第一點，用final修飾的方法無法被覆寫。

這是關鍵字final修飾方法的特性，詳細內容於後面討論。

#### 第二點，方法名稱、回傳型態、參數個數必須相同。

嗯，如果不一樣的話，就是自己再定義一個新方法了阿！！跟覆寫有什麼關係 XD

#### 第三點，子類別方法開放權限不得小於父類別方法。

簡單來說，如果父類別說這個方法是對全世界公開(public)的方法，你要覆寫就不能占為己有(private)。

**※存取修飾子的開放權限從大到小**：public -> protected -> (no modifier) -> private。

如果父類別說此方法是protected，那子類別覆寫時的修飾子必須是public或protected。

如果父類別說此方法是private，那子類別覆寫時的修飾子必須是public或protected或(no modifier)或private。

關鍵是**權限的開放範圍不得小於覆寫對象。**

以下針對三種限制用程式來說明\~

第一點，程式範例：

```java
class A{
    //       (↓關鍵字 final)
    public final void printInfo(){
        System.out.println("hello, this is A.");
    }
}
class B extends A{
    // 編譯錯誤！  ↓ 利用final修飾的方法不能被覆寫。
    public void printInfo(){
        System.out.println("hello, this is B;");
    }
}
```

在類別A的printInfo()方法利用關鍵字 final 修飾，所以任何繼承他的子類別都不能覆寫這個方法。 否則會產生編譯錯誤：『Cannot override the final method from A』。

第二點，程式範例：

```java
class A{
    public void printInfo(){
        System.out.println("hello, this is A.");
    }
}
class B extends A{
    public void printInfo2Tina(){
        System.out.println("hello Tina, nice to meet you <3");
    }
}
```

恩，就是多定義一個方法，沒什麼好說的，這根本不是覆寫。

第三點，程式範例：

```java
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()，覆寫的開放權限必須為public或protected或(no modifier)，重點就是不能小於覆寫對象，否則會發生編譯錯誤：『Cannot reduce the visibility of the inherited method from A』。
