870 likes | 1k Views
第 12 章. 抽象類別與介面. 本章重點. 12 - 1 抽象類別 (Abstract Class) 12 - 2 介面 ( Inter face) 12 - 3 介面的繼承 12 - 4 綜合演練. 12 - 1 抽象類別 (Abstract Class). 回頭看看上一章中提到多層的繼承時 , 所與的圖形類別範例 , 在 Shape 、 Circle 、 Cylinder 、 Rectangle 這幾個類別中 , Shape 這個類別其實並未被主程式用到 , 而其存在的目的只是為了讓整個繼承結構更完善。
E N D
第 12 章 抽象類別與介面
本章重點 • 12 - 1 抽象類別 (Abstract Class) • 12 - 2 介面 ( Inter face) • 12 - 3 介面的繼承 • 12 - 4 綜合演練
12 - 1 抽象類別 (Abstract Class) • 回頭看看上一章中提到多層的繼承時, 所與的圖形類別範例, 在 Shape、Circle、Cylinder 、Rectangle 這幾個類別中, Shape 這個類別其實並未被主程式用到, 而其存在的目的只是為了讓整個繼承結構更完善。 • 實際上 Shape 只是一個抽象的概念, 程式中並不會有 Shape 的物件, 而只會使用它的衍生類別如 Circle、Rectangle 等, 來建立物件。因此, 我們需要一種方法, 可以讓類別的使用者知道, Shape 這個類別並不能用來產生物件。
抽象類別 (Abstract Class) • 12-1-1 甚麼是抽象類別? • 12-1-2 抽象方法 (Abstract Method) • 12-1-3 抽象類別、抽象方法與繼承關係
12-1-1 甚麼是抽象類別? • 為了解決上述的問題, Java 提供抽象類別 (Abstract Class)的機制, 其用途即是讓我們標註某個類別僅是抽象的概念, 不應該用以產生物件。只要在類別的名稱之前加上 abstract存取控制字符, 該類別就會成為抽象類別, Java編譯器將會禁止任何產生此物件的動作。舉例來說, 在上述的範例中, Shape 類別就很適合更改為抽象類別, 因為這此類別不代表具體的物件, 真正的物件必須由其子類別產生。因此, 程式就可以改成這樣:
甚麼是抽象類別? • 由於抽象類別不能建立物件, 因此只要程式中有任何地方想要建立抽象類別的物件, 在編譯時, 就會出現錯誤, 例如以下的範例:
甚麼是抽象類別? • 如上所示, 一旦在類別定義前加上 abstract 將其宣告為抽象類別後, 在程式中要建立該類別的物件就會被視為錯誤。
12-1-2 抽象方法(Abstract Method) • 同樣的道理, 在上一章所舉的計算地價的程式範例中, Land 類別也可視為是一個抽象的概念, 表示某種形狀的土地, 真正能用來計算地價時, 都是使用其子類別 Circle、Square 等所建立的物件。因此, Land 也是標註為抽象類別的好對象:
抽象方法 (Abstract Method) • 不過讓我們仔細思考一下, Land 這個抽象類別和前述圖形類別 Shape 有些微的不同。在 Shape 類別中所定義的建構方法或其它方法, 是其子類別所共同需要的且會實際呼叫使用的;可是在 Land 類別中, 其定義的 area() 方法, 本身並不執行任何動作, 它的存在只是為了確保所有 Land 的衍生類別都會有 area() 方法而已, 至於各衍生類別 area() 方法實際要進行什麼動作, 則是由各衍生類別自行依本身的特性定義之。
抽象方法 (Abstract Method) • 但是因為方法的定義一定要包含有主體區塊, 所以在 Land 類別中的 area() 方法就定義成很無聊的傳回 0。 • 在 Java 中, 對於這種性質的方法, 可以將之標註為抽象方法 (AbstractMethod) , 如此一來, 就只需要定義方法的名稱以及所需要的參數及傳回值的型別即可, 而不需要定義其主題區塊的內容。
抽象方法 (Abstract Method) • 標註的方法很簡單, 就和抽象類別的標註方式一樣, 只要在方法名稱之前加上abs t ract 存取控制字符即可。同樣以 Land 類別為例:
抽象方法 (Abstract Method) • 第 2 行就是加上 abstract 標註的 area() 方法, 由於不需要定義其主體區塊,所以要記得在右括號之候補上一個分號 ( ; ),表示這個敘述的結束。 • 使用抽象方法時, 要特別注意, 擁有抽象方法的類別一定要標註為抽象類別。這道理很簡單, 抽象方法代表的意義是這個方法要到子類別才會真正實作, 既然如此, 就表示其所屬的類別並不完整, 自然就不應該拿來產生物件使用, 所以必須為抽象類別。
抽象方法 (Abstract Method) • 但是反過來說, 一個抽象類別卻未必要擁有抽象方法, 像是前面介紹的Shape 類別就未定義抽象方法。
12-1-3 抽象類別、抽象方法 與繼承關係 • 對於一個擁有抽象方法的抽象類別來說, 如果其子類別並沒有實作其中的所有抽象方法, 那麼這個子類別就自動變成抽象類別。例如:
抽象類別、抽象方法與繼承關係 • 由於 Child 並沒有實作繼承而來的 show() 抽象方法, 所以編譯的錯誤訊息就是說 Child 也必須是一個抽象類別。如果要正確編譯這個程式, 除了必須為 Child 類別加上 abstract 存取控制, 同時第 17 行也不能用它建立物件, 請參考以下的例子:
抽象類別、抽象方法與繼承關係 • 此範例修正了之前的錯誤, 除了在第 5 行將 Child 類別定義為 abstract (因為它沒有實作抽象方法 show()), 第 main() 方法中, 也改用有實作 show() 的Grandson 類別來建立物件, 因此可正常編譯執行。
12 - 2 介面 ( Inter face) • 前面提過, 撰寫物件導向程式的第一步, 就是分析出程式中需要哪些類別, 以及類別之間的繼承關係。不過就像我們在現實世界中所看到的, 許多『不同類』的事物, 其間又通常會具有一些相似的行為。舉例來說, 飛機和小鳥很顯然不會是同性質的類別, 而其飛的方式也不同, 但不可否認它們都具有會飛行的行為。
介面 ( Inter face) • 類似這樣的情況, 在設計程式時也會遇到:一些在繼承架構中明顯不同的類別, 它們卻有具有一些相似的行為 (特性), 造成設計類別時的困擾, 例如將明顯不同性質的類別 (例如飛機和鳥), 湊成在同一繼承架構下, 使得類別的繼承關係不合常理。因此為了不打亂原有的繼承架構, 我們可能要選擇分別在各自的類別中定義各自有的行為。 • 為了讓這樣的設計也能系統會, 不會造成不同性質的類別, 在實作它們應有的共通行為時造成遺漏或命名不一致, 在 Java 中特別提供了介面(Interface) 來描述這個共通的行為。
介面 ( Inter face) • 12-2-1 定義介面 • 12-2-2 介面的實作 • 12-2-3 介面中的成員變數
Java 不支援多重繼承 • 有些物件導向程式語言會支援多重繼承 (Multiple Inheritance), 也就是讓單一類別同時繼承自多個父類別, 如此一來可解決上述不同性質類別有共通行為的問題。 • 不過多重繼承也會使語言複雜化, 而在第一章我們就提過『, 簡單』是 Java 語言的主要特色之一, 因此當初開發 Java 語言的小組就決定讓 Java 語言不支援多重繼承, 以保持其簡單學的特色。 • 雖然如此, Java 的實用性並不因此而減少, 需要使用到多重繼承的場合, 幾乎也都可透過 Java 的介面功能達成。
12-2-1 定義介面 • 由於介面代表的是一群共通的行為, 感覺上和類別好像有些類似之處, 但其實兩者具有相當的差異, 只是外觀上定義介面也是用大括號來描述此介面的方法, 而開頭要改用 interface 來表示要定義的是介面:
定義介面 • 由於類別是用以描述實際存在的物件, 而介面則僅是用以描述某種行為方式 (例如『會飛』這件事), 所以兩者本質上有許多差異, 以下是定義介面時要注意的重點: • 介面的命名也和類別一樣, 通常都是以首字母大寫的方式, 使得其在程式中容易被識別。有些人習慣在介面的名稱前加上一個大寫字母' I", 以特別標示這個名稱是個介面。
定義介面 • 在介面中只能定義方法的型別 (傳回值) 及參數型別, 不可定義方法本體(和抽象方法相同), 這些方法預設都會自動成為 public 的抽象方法, 請記得在右括號之後加上分號。 • 由於介面通常代表某種特性, 因此介面的名稱一般都是一個形容詞, 表示可以如何的意思, 例如可用 Flying 表示『會飛』的意思。
定義介面 • 舉例來說, 前一章最後我們舉了計算地價的範例, 要計算地價時, 當然要算出土地的面積, 然而計算面積這件事, 可能是很多類別需要的功能, 所以我們可以定義一個計算面積的介面:
定義介面 • 如前所述, 不可定義方法本體, 因為每種不同形體, 其面積的計算方式也都不同, 我們也無法預知有哪些類別需要計算面積, 此處 surfacing 介面只規定了計算面積的方式名稱為 area、沒有參數、但傳回值為 double 型別。 • 如前所述, 介面中的方法預設都是公開的抽象方法, 所以通常 public 、abstract 也都省略不寫。
12-2-2 介面的實作 • 定義好介面之後, 需要使用該介面的類別, 就必須實作該介面, 實作介面時, 必須在類別名稱之後, 使用implements保留字, 再加上要實作的介面名稱。此外, 前面提過, 介面中所定義的方法會自動成為抽象方法, 因此實作介面時就必須完全實作介面中的所有方法。
介面的實作 • 我們先用上一章 Shape 類別及 Circle 類別的繼承架構, 並讓 Circle 實作Surfacing 介面:
介面的實作 • 第 18 行的 Circle 類別定義, 先繼承了 Shape 類別再以 implements 保留字表示此類別要實作 Surfacing。因此 Circle 類別必須定義於 Surfacing 介面中宣告的 area() 方法, 所以在第 27 行定義了計算圓面積的 area() 方法。如果宣告了要實作某個介面, 但類別中未定義該介面所宣告的方法, 編譯時將會出現錯誤。 • 另外, 要記住介面所提供的方法都是 public, 因此實作介面時也要將之宣告為 public, 不可將之設為 protected 或 private, 如此也會造成編譯錯誤。
12-2-3 介面中的成員變數 • 介面也可以擁有成員變數, 不過在介面中宣告的成員會自動擁有 stati cpublic final的存取控制。換句話說, 在介面中僅能定義由所有實作該介面的類別所共享的常數。舉例來說, 在剛才的例子中, 將圓周率常數定義在介面中, 而實作該介面的類別, 亦可存取該常數:
介面中的成員變數 • 在第 3 行於介面中定義了成員變數 PI, 並在第 28 行 Circle 類別的 area()方法用以計算圓面積。雖然定義 PI 時未加上任何存取控制字, 但如前述, 介面中的成員變數會自動成為 public static final, 所以在程式第 41 行也能如同存取類別 static 成員變數一般, 用介面名稱存取其值。
12 - 3 介面的繼承 • 介面之間也可以依據其相關性, 以繼承的方式來架構, 就像是在上一章中對於類別的分類一樣。不過介面的繼承與類別的繼承最大的差別, 就在於介面可以繼承多個父介面, 而類別無法繼承多個父類別。也正由於這樣的差異, 因而衍生出幾個必須注意的主題, 這些都要在這一小節中討論。
介面的繼承 • 12- 3 - 1 簡單的繼承 • 12- 3 - 2 介面的多重繼承 • 繼承多個同名的方法 • 繼承多個同名的成員變數 • 實作多重介面
12- 3 - 1 簡單的繼承 • 介面的繼承最簡單的形式就是使用 extends 保留字從指定的介面延伸,例如:
簡單的繼承 • 第 7 行中就宣告了一個繼承自介面 P 的介面 C。由於有繼承關係, 所以介面 C 的內容除了自己所定義的 getI() 方法之外, 也繼承了由 P 而來的成員變數 i 以及 show() 方法。因此, 在第 11 行實作介面 C 時, 就必須實作getI() 以及 show() 方法。