# 繼承

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

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

試想一種情況：

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

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

方法：
移動。
```

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

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

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

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

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

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

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

類別圖：

![](/files/-MLRnlfPuqW9Yh_wjPdw)

這是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）的關係，繼承樹上越頂端的父類別擁有越『一般』的特性，越底端的子類別越『特殊』。

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

看起來就像這樣：

![](/files/-MLRnlfQlKjC8DWM5gTv)

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

## 萬物之父 Object

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

![](/files/-MLRnlfR00mDuoArC4zO)

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

![](/files/-MLRnlfSYgNxSg2OmKBh)

定義類別的時候，如果沒有使用關鍵字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 開始一層一層建構下來，也是因為這樣，子類別才能擁有父類別的所有成員。

舉個例子：

![](/files/-MLRnlfYl7feepVrEiOq)

```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的建構子
```

哇塞，真是太神奇了！

看一下類別示意圖：

![](/files/-MLRnlfZyF3A3-e-JoW0)

我們要建構的是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』。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yubin551.gitbook.io/java-note/object_oriented_programming/inheritance.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
