繼承

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

為什麼需要繼承? Why Inheritance?

試想一種情況:

有一個Aminal類別,它的定義如下:

屬性:
重量、身長、年齡。

方法:
移動。

好,這個類別定義好,現在要再定義一個Dog的類別:

屬性:
重量、身長、年齡、毛色。

方法:
移動、吃、睡、吠叫。

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

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

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

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

類別圖:

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

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

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

Java程式要如何表示繼承關係呢? 利用關鍵字 extends:

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

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

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

一般化、特殊化

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

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

看起來就像這樣:

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

萬物之父 Object

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

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

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

關鍵字 this 、 super

this跟super都是關鍵字,都是reference。 指到哪裡呢?

this

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

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

自己的建構字 this(.);

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

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

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

特別要注意的是:

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

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

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

執行結果:

super

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

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

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()方法,但很明顯,父類別太過於一般化,沒辦法滿足子類別需要的功能(以此為例就是資訊量不夠),所以子類別『覆寫(override)』了父類別的方法,創造了特殊且適合自己的getInfo()。

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

測試一下執行結果:

執行結果:

層層初始化

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

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

舉個例子:

建構一個C物件試試:

執行結果:

哇塞,真是太神奇了!

看一下類別示意圖:

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

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

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

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

以上的程式來說,就像這樣:

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

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

觀察底下程式,想想執行結果:

主程式:

執行結果:

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

存取修飾子 protected

存取修飾子的章節提過,現在剛好提到繼承再拿出來討論。

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

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

再看Dog的程式碼:

覆寫的存取修飾限制

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

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

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

舉個例子:

測試程式:

執行結果:

上述程式中,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。

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

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

第一點,程式範例:

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

第二點,程式範例:

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

第三點,程式範例:

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

Last updated

Was this helpful?