Microsoft Word - ACL CH06

Similar documents
運算子多載 Operator Overloading

0 0 = 1 0 = 0 1 = = 1 1 = 0 0 = 1

投影片 1

Microsoft Word - 投影片ch11

Microsoft PowerPoint - chap08.ppt

1: public class MyOutputStream implements AutoCloseable { 3: public void close() throws IOException { 4: throw new IOException(); 5: } 6:

主程式 : public class Main3Activity extends AppCompatActivity { ListView listview; // 先整理資料來源,listitem.xml 需要傳入三種資料 : 圖片 狗狗名字 狗狗生日 // 狗狗圖片 int[] pic =new

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

Microsoft Word - chap12.doc

PowerPoint Presentation

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课

投影片 1

投影片 1

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

投影片 1

The Embedded computing platform

Microsoft Word - 物件導向編程精要.doc

10-2 SCJP SCJD 10.1 昇陽認證 Java 系統開發工程師 的認證程序 Java IT SCJD

Chapter 3 Camera Raw Step negative clarity +25 ] P / Step 4 0 ( 下一頁 ) Camera Raw Chapter 3 089

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

CU0594.pdf

資料結構之C語言重點複習

Microsoft Word - JAVA Programming Language Homework I ans

EJB-Programming-4-cn.doc

840 提示 Excel - Excel -- Excel (=) Excel ch0.xlsx H5 =D5+E5+F5+G5 (=) = - Excel 00

單步除錯 (1/10) 打開 Android Studio, 點選 Start a new Android Studio project 建立專案 Application name 輸入 BMI 點下 Next 2 P a g e


Chapter 9: Objects and Classes

Microsoft PowerPoint htm

Python a p p l e b e a r c Fruit Animal a p p l e b e a r c 2-2


任務二 : 產生 20 個有炸彈的磚塊, 放在隨機的位置編輯 Block 類別的程式碼 import greenfoot.; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo) Write a description of class

Microsoft PowerPoint - L17_Inheritance_v4.pptx

男人的大腦 女人的大腦

Microsoft PowerPoint - P766Ch06.ppt

Microsoft Word - ch04三校.doc

《大话设计模式》第一章


運算子多載 Operator Overloading

EJB-Programming-3.PDF

untitled

Microsoft Word - ACL chapter02-5ed.docx

无类继承.key

戒菸實務個案自助手冊105年Ver.2

Microsoft Word - 投影片ch15

老人憂鬱症的認識與老人自殺問題

用手機直接傳值不透過網頁連接, 來當作搖控器控制家電 ( 電視遙控器 ) 按下按鍵發送同時會回傳值來確定是否有送出 問題 :1. 應該是使用了太多 thread 導致在傳值上有問題 2. 一次按很多次按鈕沒辦法即時反應


詞 彙 表 編 號 詞 彙 描 述 1 預 約 人 資 料 中 文 姓 名 英 文 姓 名 身 份 證 字 號 預 約 人 電 話 性 別 2 付 款 資 料 信 用 卡 別 信 用 卡 號 信 用 卡 有 效 日 期 3 住 房 條 件 入 住 日 期 退 房 日 期 人 數 房 間 數 量 入

理性真的普遍嗎 注意力的爭奪戰 科學發展 2012 年 12 月,480 期 13

ACI pdf


Microsoft PowerPoint - VB14.ppt

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

一、

二次曲線 人們對於曲線的使用及欣賞 比曲線被視為一種數學題材來探討要早 得多 各種曲線中 在日常生活常接觸的 當然比較容易引起人們的興趣 比如 投擲籃球的路徑是拋物線 盤子的形狀有圓形或橢圓形 雙曲線 是較不常見的 然而根據科學家的研究 彗星的運行軌道是雙曲線的一部 分 我們將拋物線 圓與橢圓 雙曲

(TestFailure) JUnit Framework AssertionFailedError JUnit Composite TestSuite Test TestSuite run() run() JUnit

<4D F736F F F696E74202D20332D322E432B2BC3E6CFF2B6D4CFF3B3CCD0F2C9E8BCC6A1AAD6D8D4D8A1A2BCCCB3D0A1A2B6E0CCACBACDBEDBBACF2E707074>

Microsoft Word - ACI chapter00-1ed.docx

javaexample-02.pdf

1 Framework.NET Framework Microsoft Windows.NET Framework.NET Framework NOTE.NET NET Framework.NET Framework 2.0 ( 3 ).NET Framework 2.0.NET F


<B8D5C5AAA5BB2E706466>

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

软件工程文档编制

愛滋實務與治理的政治 - 綜合論壇 以及面對這一連串以 責任 為架構衍生出來的愛滋政策如何造就了台灣現在的愛滋處境

The golden pins of the PCI card can be oxidized after months or years

多媒體應用 13 新增專案並完成版面配置 <ExMusic01> <activity_main.xml> ImageView ID imgplay ImageView ID imgstop ImageView ID imgfront TextView ID txtsong TextView ID t

Spyder Anaconda Spyder Python Spyder Python Spyder Spyder Spyder 開始 \ 所有程式 \ Anaconda3 (64-bit) \ Spyder Spyder IPython Python IPython Sp

Strings

輕鬆學 Dreamweaver CS5 網頁設計..\Example\Ch0\ \.html..\example\ch0\ \mouse.txt..\example\ch0\ \ _Ok.html 學習重點 JavaScript 複製程式碼 mouse.txt Ctrl+C Ctrl+C 0-4

運算子多載 Operator Overloading

踏出C++的第一步

第二章 簡介類別

chp6.ppt

Microsoft Word - chap05.doc

Strings

Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0,

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

1

前言 人類的歷史, 因 一個簡單的思維 而改變! 1776 Thomas Paine COMMON SENSE

選擇學校午膳供應商手冊適用於中、小學 (2014年9月版)

96年特種考試第一次司法人員考試試題解答

Java 程式設計初階 第 5 章:基本輸出入 & 流程控制

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款

Microsoft Word - 投影片ch13

17-72c-1

Microsoft Word - 01.DOC

1 狂想 凌駕地平線 雙門轎跑的動感 豪華休旅的性能 別懷疑 夢想中的合而為一 正是駕馭它的實際感 受 來自突破型態的車款 The GLC Coupé 將性能 豪華與美感設計融為一體 集合所有 人偏愛的新世代完美座駕 無論都會 野地 它總用愜意從容的態勢 定義著 何謂突 破 何謂改寫

untitled

(procedure-oriented)?? 2

Microsoft PowerPoint - 07-overloaded.ppt

書面

untitled

Microsoft PowerPoint - chap07.ppt

Microsoft PowerPoint - 13_ClassAndObj.ppt

治療血管的雷射 port wine stain 1988 FDA KTP KTP

Transcription:

衍生類別與繼承 物件導向程式設計有一個很重要的特性, 它允許您基於一個已定義好的類別, 以此建立新的類別 用來建立新類別的基礎類別可以是一個已定義好的類別, 或是 Java 裡的一個標準類別, 也可以是由其它類別所定義的類別 ( 也許它來自一個專門支援應用領域的套件 ) 本章所專注的重點在於 : 如何重複使用已存在的類別, 例如基於已存在的類別來建立新類別 ; 並探討此種類別的完整能力以及它所提供的額外功能 ; 此外, 還有另一個重要的相關主題 介面 (interfaces), 何謂介面 如何使用它們 本章涵蓋的內容如下 : 如何基於已存在的類別來定義新類別, 以達到重複使用類別的目的? 什麼是同名異式, 如何定義程式以獲取同名異式的優點? 什麼是 abstract 函式? 什麼是 abstract 類別? 什麼是介面, 如何定義自訂的介面? 如何使用類別的介面? 介面如何幫助您實作同名異式類別? 使用已存在的類別 先從專業用語開始瞭解 從一個已存在的類別來定義新類別, 此種行為稱作衍生 ( derivation ) 由衍生的關係可知: 新類別, 也就是衍生類別 (derived class), 可視為此類別的直系子類別 (direct subclass); 原本的類別則可稱為基礎類別 (base class), 因為它是衍生類別定義的基本內容, 而原本的類別也可視為衍生類別的父類別 (superclass) 此外, 衍生類別又可再衍生出另一個新類別, 而新建立的類別還可再衍生出更新的類別 以此類推 有關衍生類別, 我們用圖 6-1 來加以說明 :

262 Chapter 6 衍生類別與繼承 圖 6-1 上圖顯示了 3 個類別的階層關係, 如果有需要的話, 可以有任意多層的類別 現在再來考慮一個更具體的範例 定義一個 Dog 類別, 表示各種不同種類的狗 class Dog { // Members of the Dog class... 在此類別中可能有一個用來辨識狗名的資料成員, 例如 "Lassie" 或 "Poochy", 以及另一個用來辨識種類的資料成員, 例如 "Border Collie" 或 "Pyrenean Mountain Dog" 由這個 Dog 類別, 可衍生出一個 Spaniel 類別, 用來代表那些毛色漂亮而且耳朵長的狗 : class Spaniel extends Dog { // Members of the Spaniel class... 使用 extends 關鍵字, 以表示 Dog 是 Spaniel 的基礎類別, 所以 Spaniel 類別的物件將具有從 Dog 類別繼承而來的成員, 再加上在 Spaniel 類別中所定義的成員 雖然每隻毛色漂亮而且耳朵長的狗, 都有不同的名字, 不過對於 Spaniel 類別的所有實體來說, 它們的種類都是 "spaniel" Spaniel 類別也許還有其它額外的資料成

類別的繼承 263 員, 用來描述毛色漂亮而且耳朵長的狗具有何種特性 稍後將看到如何安排基礎類別的資料成員, 讓它們可以被適當地設定 Spaniel 物件是 Dog 物件的一個特殊化的實體, 這也可以反映到現實生活裡 ; 一隻毛色漂亮而且耳長的狗, 它就是一隻狗, 擁有一般狗的特性, 不過牠本身還有屬於自己的某些特性, 讓我們可以分辨出這隻狗與其它狗之間的差別 繼承的機制將包含基礎類別的全部屬性, 以 Dog 來說, 其特性會繼承給衍生類別, 這與現實生活非常地類似 衍生類別的成員定義了一些與基礎類別不同的屬性, 所以當您由一個類別衍生出另一個類別時, 可想像衍生類別的物件是基礎類別物件的一種特殊化物件 以另外一種想法來說, 基礎類別定義了一個物件的集合, 而衍生類別則定義這些物件為其子集合, 並且其中還具有個別的特徵 類別的繼承 總之, 由基礎類別衍生出一個新類別時, 在建立新類別時, 它會附加一些新的內容到類別定義裡 在新類別所定義的額外成員, 可制定出衍生類別物件與基礎類別物件之間的不同 ; 任何在新類別內所定義的成員, 必須有別於基礎類別已定義的成員 以 Spaniel 類別來說, 它是由 Dog 衍生而來的新類別, 那些定義在 Dog 類別裡 用來儲存名稱和種類的資料成員, 都會自動加入 Spaniel 類別裡 Spaniel 物件可說是包含了一個完整的 Dog 物件在它裡面, 包括它所有的資料成員及物件在內 不過, 這並不代表所有 Dog 類別所定義的成員, 都可用於 Spaniel 類別本身的函式中 ; 有些可以, 有些則不行 在衍生類別裡, 若基礎類別的內容在衍生類別裡可被存取, 就稱為類別繼承 (class inheritance) 基礎類別的可繼承成員 (inherited member) 在衍生類別裡是可被存取的 ; 如果基礎類別裡的成員, 在衍生類別裡無法被存取, 則此成員為不可繼承的成員 ; 不過, 雖然基礎類別的成員可能是無法被繼承的, 但它仍會是衍生類別物件的一部份 衍生類別的可繼承成員是此類別中的正式成員, 且在類別函式中可以無限制地使用它 衍生類別型態的物件將包含所有基礎類別的可繼承成員, 包含欄位和函式在內 請注意, 衍生類別的物件會包含一個完整的基礎類別物件, 當然, 其中也包含所有不可繼承的欄位和函式 關於繼承是如何運作的, 以及基礎類別成員的存取屬性會如何影響它在衍生類別中的可視性, 這部份的內容以後會再作更深入的討論 對於如何定義和使用衍生類別, 必須考量以下幾個觀點 首先, 要知道基礎類別的哪些成員在衍生類別中是可繼承的 讓我們分別來看這對於資料成員及函式有些什麼隱含的意義 其中有些微妙的地方是我們應該要清楚了解的, 在建立衍生類別的物件時, 我們會更進一步地看看究竟發生了什麼事 在剛剛所說的這些內容裡, 還有一些要注意的技巧, 現在, 讓我們先由基礎類別的可繼承資料成員開始!

264 Chapter 6 衍生類別與繼承 繼承資料成員 圖 6-2 表示哪些存取屬性允許類別成員被繼承到子類別裡 由下圖可知, 子類別與基礎類別位在同一套件或不在同一套件的差別, 請記得, 繼承所代表的是衍生類別成員的存取性, 並非表示該成員存在與否 圖 6-2 請注意, 類別本身可指定為 public, 這使得任何套件都可存取此類別 ; 若類別不是宣告為 public, 該類別就只能被同一套件裡的類別存取 這表示, 您無法以其它套件的類別來定義 non-public 類別型態的物件 ; 也就是說, 對於一個新類別, 其所在的套件內若不包含其基礎類別, 則此基礎類別必須被宣告為 public; 若基礎類別不為 public, 就無法直接由套件外面來使用此類別 正如您由圖 6-2 所見, 在同一個套件裡定義子類別, 除了基礎類別的 private 資料成員以外, 基礎類別的其它項目都會被繼承 如果在基礎類別的套件外定義子類別, 則 private 資料成員是不會被繼承的 ; 此外, 在基礎類別中未宣告存取屬性的資料成員, 也不會被繼承 無論在任何情況下, 基礎類別裡定義為 private 的成員都無法被繼承 基礎類別 MyClass 必須在 Package1 中宣告為 public, 否則的話, 它就沒有辦法從 Package2 來存取, 成為 SubClass2 的基礎類別 現在, 您應該已經明確地瞭解存取屬性所代表的意義 對類別成員來說,public 是最不嚴格的存取屬性, 因為 public 成員在任何地方都可使用 ; 接下來是 protected, 它會防止套件外部的類別來存取, 不過它並不會限制繼承性 當類別成員未指定任何存取屬性, 則其存取屬性就侷限於同一套件內的類別, 而且不允許定義在其它套件內的子類別來繼承 最嚴格的存取屬性是 private, 它將類別成員的存取性侷限於同一類別內

類別的繼承 265 繼承的規則可應用於類別變數及實體別數裡 ( 類別變數是指被定義為 static 的變數 ) 請回想一下,static 變數只有一個實體存在, 凡宣告為 static 的變數只會出現一組, 且由該類別的所有物件所共用 ; 然而, 實體變數則是在每個物件中各有一組屬於自己的實體 例如, 如果基礎類別的變數宣告為 private 且為 static, 則衍生類別將無法繼承 ; 若變數宣告為 protected 且為 static, 則此變數會被繼承, 並由衍生類別型態的所有物件所共用, 如同基礎類別型態的物件一樣 隱藏資料成員 在衍生類別中, 您可以定義一個與基礎類別具有相同名稱的資料成員 一般來說, 這並非我們所推薦的設計方式, 不過確實可能無心發生這樣的事情 在此種狀況下, 基礎類別的資料成員仍會被繼承, 不過它會被同名的衍生類別成員隱藏起來 隱藏的機制並不會考慮個別的型態或存取屬性是否相同, 只要基礎類別的成員與衍生類別的成員名稱相同即可 此時, 無論如何使用類別成員的名稱, 都會使用到衍生類別所定義的成員 ; 若要使用由基礎類別所繼承的同名成員, 則必須加上 super 關鍵字, 表示您所要使用的成員是屬於父類別的 現在, 假設基礎類別有一個 value 資料成員, 而衍生類別也同樣有一個同名的資料成員 ; 在衍生類別中,value 所代表的是衍生類別的成員, 而 super.value 所代表的則是由基礎類別繼承而來的成員 請注意, 不可以使用 super.super.somthing 來使用被隱藏之基礎類別的成員名稱 在大多數的情況下, 並不需要以這種方式來使用繼承而來的資料成員, 因為一開始就會審慎地避開那些重複的名稱 不過, 萬一用來作為基礎類別的類別是以增加資料成員的方式修改而成的, 則重複的情形就容易發生了 例如,Java 函式庫的類別, 或由他人所設計與維護之套件的一些類別 因為您的程式碼並不會假設基礎類別的成員會和衍生類別的資料成員有相同的名稱, 此時, 將繼承而來的成員隱藏起來, 將會符合您的希望, 同時, 這種方法可在不破壞程式的情況下修改基礎類別 繼承函式 基礎類別裡的一般函式, 也就是那些不為建構式的函式, 它們也會和基礎類別之資料成員一樣, 被傳遞給衍生類別 在基礎類別裡宣告為 private 的函式是不會被繼承的 ; 而那些未指定存取屬性的函式, 唯有當您所定義的衍生類別和基礎類別位於同一個套件時, 才可以繼承 ; 至於其它的函式, 都可以繼承 建構式有別於一般的函式 在基礎類別裡的建構式, 無論它的屬性為何, 絕對不會被繼承 從考慮建構式一個衍生類別的物件來探討 在類別階級制度下, 建構式有何複雜之處

266 Chapter 6 衍生類別與繼承 衍生類別的物件本章一開始曾說過, 衍生類別是基礎類別的衍生 ; 這可不是隨便說說的, 它確實是這樣 如同之前已經說過好幾次的, 繼承代表基礎類別的哪些成員可在衍生類別裡被存取的 ; 而不是指基礎類別的哪些成員存在於衍生類別的物件 子類別的物件除了包含基礎類別的所有成員外, 還包含衍生類別所定義的新成員 ( 如圖 6-3 所示 ) 圖 6-3 基礎類別的所有成員都在衍生類別的物件裡, 但在衍生類別的函式中, 有些基礎類別的成員是不允許被存取的, 但這並不表示它們是多餘的負擔 ; 它們是衍生類別物件必要的成員 Spaniel 物件需要利用 Dog 的所有屬性來製作一個 Dog 物件, 即使對 Spaniel 物件而言, 有些屬性是不會存取的 當然, 由衍生類別繼承而來的基礎類別函式可存取所有基礎類別的成員, 包含那些無法繼承的 雖然基礎類別的建構式不會被繼承到衍生類別中, 您仍可呼叫它們來將基礎類別的成員初始化 ; 不只如此, 如果不由衍生類別的建構式呼叫基礎類別的建構式, 編譯程式將會自動為您安排這點 這麼做的主要理由是因為 衍生類別物件裡其實有一個基礎類別的物件, 要將衍生類別物件裡所包含的基礎類別部分初始化, 最好的方式就是使用基礎類別的建構式 要更瞭解這一點, 讓我們來看看實際上的運作方式

類別的繼承 267 衍生一個類別 舉一個簡單的範例, 假設定義一個類別來表示一隻動物, 其內容如下 : public class Animal { public Animal(String atype) { type = new String(aType); public String tostring() { return "This is a " + type; private String type; 在此有個 type 成員, 可用來辨別動物的種類及建構式所設定的數值 另外有個 tostring() 函式, 可以字串表示此類別的物件 現在, 由基於類別 Animal 來定義另一個 Dog 類別 此類別可以馬上定義完成, 並且不會影響到 Animal 類別的定義 Dog 類別的基本定義內容如下 : public class Dog extends Animal { // constructors for a Dog object private String name; private String breed; // Name of a Dog // Dog breed 在此類別定義中, 在子類別的定義裡使用了 extends 關鍵字, 以確認父類別的名稱 Dog 類別只由 Animal 類別繼承 tostring() 函式, 因為 private 資料成員和建構式是無法繼承的 當然,Dog 物件會有一個 type 資料成員, 它必須被設定為 "Dog", 只不過這個成員無法在 Dog 類別所定義的函式中作存取 此外, 衍生類別裡加入了兩個新的實體變數 :name 成員可儲存狗的名稱 ;breed 成員則可記錄狗的種類 接下來, 我們要建立 Dog 類別的物件 衍生類別的建構式 在 Dog 子類別中定義兩個建構式, 一個只接受狗的名稱為引數, 另一個則接受 Dog 物件的名稱及種類為引數 對於此衍生類別的所有物件, 都必須確認 private 基礎類別的成員 type 有適當地初始化 ; 這一點可以在衍生類別的建構式呼叫基礎類別的建構式時達成, 其內容如下 : public class Dog extends Animal { public Dog(String aname) { super("dog"); // Call the base constructor name = aname; // Supplied name breed = "Unknown"; // Default breed value public Dog(String aname, String abreed) { super("dog"); // Call the base constructor

268 Chapter 6 衍生類別與繼承 name = aname; breed = abreed; // Supplied name // Supplied breed private String name; // Name of a Dog private String breed; // Dog breed 衍生類別的這個敘述將會呼叫基礎類別的建構式 : super("dog"); // Call the base constructor 在此使用 super 關鍵字作為函式名稱, 以此呼叫父類別的建構式 (Dog 類別的直系基礎類別為 Animal 類別 ); 此敘述會將 private 成員 type 初始為 "Dog", 這是由傳遞給基本類別建構式的引數所指定的 呼叫父類別的建構式, 通常都使用這種方式 在子類別中使用名稱 super, 而非使用建構式的名稱 Animal super 關鍵字在衍生類別裡還有其他的用處 ; 之前曾經看到可在成員名稱前加上 super, 用來存取基礎類別的隱藏成員 呼叫基礎類別的建構式從衍生類別的建構式中, 應該要呼叫基礎類別的建構式, 而基礎類別建構式的呼叫敘述必須是衍生類別建構式主體內的第一個敘述 ; 如果衍生類別建構式的第一個敘述不是呼叫基礎類別建構式, 編譯程式就會為自動呼叫預設的基礎類別之建構式, 其內容如下 : super(); // Call the default base constructor 很不幸地, 即使這個敘述自動被加入程式中, 還是有可能會產生一個編譯程式的錯誤訊息 這究竟是怎麼回事呢? 在類別裡定義自己的建構式時, 例如 Animal 類別, 其中並沒有由編譯程式所建立的預設建構式 編譯程式假設您對物件結構的詳細內容會完全地負責, 其中還包含預設建構式在內 ; 因此, 如果基礎類別裡沒有定義自己的預設建構式 ( 它是一個不使用引數的建構式 ), 當編譯程式從衍生類別的建構式裡, 自動呼叫預設建構式時, 就會產生一個訊息, 說明建構式並不存在 測試一個衍生類別 用以下的程式碼來測試 Dog 類別, 其程式內容如下 : public class TestDerived { public static void main(string[] args) { Dog adog = new Dog("Fido", "Chihuahua"); // Create a dog Dog stardog = new Dog("Lassie"); // Create a Hollywood dog System.out.println(aDog); // Let's hear about it System.out.println(starDog); // and the star

類別的繼承 269 當然,Dog 和 Animal 類別定義檔必須和 TestDerived.java 放在同一個目錄下 這個 範例的輸出訊息如下 : This is a Dog This is a Dog 程式如何運作在此範例中建立了兩個 Dog 物件, 之後再用 println() 函式輸出它們的訊息 ; 以這種方式執行 println(), 編譯程式會自動呼叫該類別的 tostring() 函式 您可以試著將衍生類別, 建構式內呼叫 super() 的敘述變成註解, 看看編譯程式對呼叫預設基礎類別之建構式的影響為何 在此可以成功地呼叫繼承而來的 tostring() 函式, 但這個類別只認識基礎類別的資料成員 然而, 我們至少知道 private 的 type 成員已經正確地設定好了, 接下來真正需要的就是衍生類別本身的 tostring() 版本 覆蓋基礎類別之函式 在衍生類別裡, 您可以定義基礎類別已有之相同簽章的函式, 此時, 衍生類別的新版函式, 其存取屬性可與基礎類別的相同 或是較不嚴格, 它絕對不能比基礎類別之原函式的存取限制更加嚴格 也就是說, 如果基礎類別中有個函式宣告為 public, 則在衍生類別中的重新定義的函式, 也必須宣告為 public; 在這種情況下, 不可以忽略衍生類別的存取屬性, 也不可將它指定為 private 或 protected 以這種方式定義基礎類別中已有之相同簽章的新版函式時, 衍生類別的物件將會呼叫衍生類別的新版函式, 而不是呼叫由基礎類別繼承而來的原函式 衍生類別的函式將會覆蓋 (override) 基礎類別的函式 ; 不過基礎類別的函式仍然存在, 並且依舊可在衍生類別裡被呼叫 現在, 讓我們來看看在衍生類別中覆蓋函式的動作 覆蓋一個基礎類別的函式 將新版本的 tostring() 函式定義加入衍生類別 Dog 的定義中, 其內容如下 : // Present a dog's details as a string public String tostring() { return "It's " + name + " the " + breed; 此範例修改後, 其輸出結果如下 : It's Fido the Chihuahua It's Lassie the Unknown

270 Chapter 6 衍生類別與繼承 程式如何運作在 Dog 類別中的 tostring() 函式覆蓋了基礎類別的函式, 因為它們具有相同的簽章 回憶上一章的內容, 函式的簽章是由其名稱及參數項目來決定的, 所以,Dog 物件現在呼叫 tostring() 函式時, 不管是不是很明確, 都會呼叫此函式, 而非基礎類別中的 tostring() 函式 請注意, 原來的 tostring() 函式被宣告為 public, 您應該感到很高興 當覆蓋一個基礎類別的函式時, 並無法將新版函式的存取屬性設為比基礎類別中被覆蓋的函式還要嚴格 由於 public 是最不嚴格的存取屬性, 因此, 新版函式無法選擇其它的存取屬性 理想上, 我們想要輸出基礎類別的 type 成員, 不過, 在衍生類別裡無法參考此成員, 因為它並沒有被繼承 然而, 我們仍可呼叫基礎類別的 tostring() 函式版本, 此時, 關鍵字 super 就派上用場了 從衍生類別呼叫一個基礎類別的函式 重新撰寫衍生類別的 tostring() 函式的版本, 讓它先呼叫基礎類別的函式, 其內容如下 : // Present a dog's details as a string public String tostring() { return super.tostring() + "\nit's " + name + " the " + breed; 再次執行此範例, 其輸出結果如下 : This is a Dog It's Fido the Chihuahua This is a Dog It's Lassie the Unknown 程式如何運作關鍵字 super 是指定辨別由衍生類別之版本所隱藏的基礎類別之 tostring() 版本 我們在衍生類別中以相同名稱來隱藏父類別之資料成員 呼叫基礎類別的 tostring() 版本將會回傳物件之基本部分的 String 物件 接著再附加上物件衍生部分的訊息, 以得到衍生類別獨有的 String 物件

選擇基礎類別的存取屬性 271 選擇基礎類別的存取屬性 子類別的存取屬性有哪些選擇, 以及就類別繼承來說, 存取屬性會造成什麼影響, 相信這些內容您都已經瞭解了 不過, 要如何決定究竟使用哪一種存取屬性呢? 以下有些簡單且快速的原則 選用什麼存取屬性, 主要決定於類別將來的用途 此外, 有一些方針是您應該要加以考慮的, 它們所採用的觀念都是基本物件導向原則, 其內容如下 : 用來建立類別外部介面的函式應該被宣告為 public 當衍生類別沒有覆蓋函式時, 基礎類別的 public 函式將會被繼承, 並且可以完全地作為衍生類別外部介面的一部份 在一般的情形下, 不應該將資料成員設為 public, 除非它們是普遍使用的常數 若期望他人會使用您的類別作為基礎類別, 請將資料成員設為 private, 並提供 public 函式來存取與維護該成員, 如此將可讓您的類別更具安全性 這種方式可控制衍生類別的物件如何使用基礎類別的資料成員 當基礎類別的資料成員為 protected 時, 則允許它們被同一套件的其它類別作存取, 同時又可防止被不同套件的類別直接存取 設為 protected 的基礎類別成員可被繼承到子類別, 因此它們可在衍生類別的實作裡使用 例如, 要讓一個套件裡的類別不受限制的存取同一套件內的任何資料成員時, 即可以使用 protected 關鍵字, 因為它們可以很緊密結合的運作, 而對不同套件的存取又可限制於其子類別 忽略類別成員的存取屬性可讓此成員被同一套件內的類別直接使用, 同時可以防止不同套件的子類別繼承該成員 若從其它套件的角度來看, 這個成員像是 private 的 同名異式 類別的繼承不只是重新使用已定義好的類別作為基礎類別來定義新類別的基本資料而已 ; 此外, 在撰寫應用程式時, 有一種稱為同名異式 (polymorphism) 的機制, 可增加程式的彈性 那麼, 什麼是同名異式呢? 同名異式 這個字通常代表具有產生數個不同格式或形狀的能力, 而就程式設計而言, 它代表一個指定型態的變數具有參考不同型態物件的能力, 以及自動呼叫該變數參考物件型態所特有的函式 這使得單一函式呼叫可有不同的行為, 至於行為為何, 主要取決於呼叫對應到哪種物件型態 我們在圖 6-4 做了說明

272 Chapter 6 衍生類別與繼承 圖 6-4 要擁有同名異式的功能, 還要必須完成一些事項, 讓我們一步步地介紹它 首先, 同名異式會和衍生類別的物件一同作用 此外, 它也會取決於我們尚未見過的類別階級制度的新能力 到目前為止, 我們使用過一個指定型態的變數來參考相同型態的物件, 衍生類別為此帶來了一些彈性 當然, 我們可以用衍生類別型態的變數儲存表示衍生類別之物件的一個 reference, 不過我們也可以將這個 reference 儲存到任何直系或間接基礎類別型態的變數中 不只如此, 一個衍生類別物件的 reference 必須儲存在直系或間接類別型態的變數, 這樣它才具有同名異式的功用 例如, 如上圖表示,Dog 型態的變數可用來儲存任何由 Dog 衍生而來之型態的物件的 reference; 如果 Dog 類別是由 Animal 類別衍生而來的, 那麼 Animal 型態的變數就可以用來參考 Spaniel Chihuahua 或 Collie 等由 Dog 衍生出來的物件 同名異式表示在呼叫函式時, 相關物件的真正型態可決定哪個函式被呼叫, 而不是由儲存參考物件之變數的型態來決定的 如上圖所示, 如果 adog 包含 Spaniel 物件的 reference, 則 Spaniel 物件的 bark() 函式就會被呼叫 ; 如果這個變數儲存的是 Collie 物件的 reference, 則 Collie 物件的 bark() 函式就會被呼叫 在呼叫函式時, 若要使用同名異式的機制, 函式本身必須是基礎類別的成員 因此, 在此範例中,Dog 類別必須包含 bark() 函式, 而衍生類別也都必須有此函式 ; 如果該函式不是基礎類別的成員, 則無法使用基礎類別的變數呼叫衍生類別物件的函式

同名異式 273 任何在衍生類別的函式定義, 都必須和基礎類別的函式具有相同的特性以及相同的回傳型態, 且存取屬性也不能比基礎類別更嚴格 具有相同特性的函式, 它們會有相同的名稱, 並且在參數列中會有相同個數之參數, 而每個對應之參數也都是相同的型態 當我們在定義同名異式的時候, 對於回傳值會較具有彈性 以同名異式來說, 在衍生類別中, 函式的回傳值必須要與基礎類別的函式相同, 或者要是基礎類別型態之子類別的型態 當回傳的數值是不同的型態時, 如果衍生類別內之函式的回傳型態是基礎類別中回傳型態的子類別, 這些回傳型態可以被稱為共變 (covariant) 因此, 由衍生類別函式所回傳之物件的型態, 只是基礎類別函式所回傳之型態的一種特殊型態 舉例來說, 假設我們在基礎類別 Animal 中定義一個函式, 並且其回傳型態為 Animal: public class Animal { Animal createcreature() { // Code to create an Animal object and return a reference to it... // Rest of the class definition... 在衍生類別中, 我們可以像這樣來重新定義 createcreature() 函式 : public class Dog extends Animal { Dog createcreature() { // Code to create a Dog object and return a reference to it... // Rest of the class definition... 如這裡所撰寫的一樣, 只要衍生類別之函式的回傳型態是基礎類別型態的子類別, 即使回傳型態是不一樣的, 仍然具有同名異式的行為 若想要使用同名異式, 必須滿足以下的條件 : 衍生類別物件的函式呼叫必須透過基礎類別型態的變數 被呼叫的函式必須要定義在衍生類別中 被呼叫的函式必須是基礎類別的成員 函式的特性在基礎類別和衍生類別中, 必須是一樣的 函式回傳值型態在基礎類別和衍生類別, 若不是相同的, 就必須具有共變特性 衍生類別的函式, 其存取屬性不能比基礎類別的更嚴格使用基礎類別型態的變數來呼叫函式時, 同名異式會以變數所儲存的物件型態來選擇被呼叫的函式, 而不是以變數本身的型態來決定 因為基礎類別型態的變數可儲存任何衍生類別之物件的 reference, 變數裡儲存的是哪一種物件, 要到程式執行後才知道 因此, 執行哪個函式的選擇必須由程式執行時 動態地決定, 程式編譯後尚無法決定 在稍早的說明裡,bark() 函式是以 Dog 型態的變數來呼

274 Chapter 6 衍生類別與繼承 叫, 而究竟要呼叫哪個函式, 主要就是由變數所參考的物件來決定 如同即將看到的, 在使用物件的程式設計上, 這種方式將會引進全新的能力階級, 這也隱含著程式可以自動立即適應 容納並處理不同種類的資料 請注意, 同名異式只對函式有效, 對資料成員是無效用的 當存取一個類別物件的資料成員時, 變數型態會決定資料型態屬於哪個類別 也就是說, 代表 Dog 型態的變數只可用來存取 Dog 類別的資料成員 ; 即使變數是用來參考 Spaniel 型態的物件, 也只可以使用此變數來存取 Spaniel 物件裡 屬於 Dog 這部分的資料成員 使用同名異式 如同您已看到的, 同名異式主要是以宣告父類別型態的變數來指定子類別型態的物件 例如, 宣告以下變數 : Animal theanimal = null; // Declare a variable of type Animal 您可以很高興的將它用在參考 Animal 類別的任何一種子類別的物件, 例如, 以此變數參考一個 Dog 型態的物件, 其方法如下 : theanimal = new Dog("Rover"); 在宣告 theanimal 變數時, 也可一併將它初始化, 使其參考一個物件, 方法如下 : Animal theanimal = new Dog("Rover"); 此原則的適用範圍相當廣泛 對於基礎類別, 無論是直系或間接, 都可使用基礎類別型態的變數 儲存任何衍生類別型態的物件的 reference 將前一個範例延伸, 看看究竟是什麼魔法達到此功能 在 Dog 類別加入一個新的函式, 表示 Dog 所發出的聲音, 然後再加上一些新的子類別, 代表其它種類的動物 加強 Dog 類別 首先, 增加一個代表狗所發出的聲音之函式, 以加強 Dog 類別, 其內容如下 : public class Dog extends Animal { // A barking method public void sound() { System.out.println("Woof Woof"); // Rest of the class as before... 由 Animal 類別衍生出 Cat 類別, 其內容如下 : public class Cat extends Animal { public Cat(String aname) { super("cat"); // Call the base constructor name = aname; // Supplied name breed = "Unknown"; // Default breed value

同名異式 275 public Cat(String aname, String abreed) { super("cat"); // Call the base constructor name = aname; // Supplied name breed = abreed; // Supplied breed // Return a String full of a cat's details public String tostring() { return super.tostring() + "\nit's " + name + " the " + breed; // A miaowing method public void sound() { System.out.println("Miiaooww"); private String name; private String breed; // Name of a cat // Cat breed 為了讓內容多一些, 再衍生另一個鴨子類別, 其內容如下 : public class Duck extends Animal { public Duck(String aname) { super("duck"); // Call the base constructor name = aname; // Supplied name breed = "Unknown"; // Default breed value public Duck(String aname, String abreed) { super("duck"); // Call the base constructor name = aname; // Supplied name breed = abreed; // Supplied breed // Return a String full of a duck's details public String tostring() { return super.tostring() + "\nit's " + name + " the " + breed; // A quacking method public void sound() { System.out.println("Quack quackquack"); private String name; private String breed; // Duck name // Duck breed 若需要練習的話, 可將整個農家的動物都加進來, 不過 3 種動物已足以表示同名異式的運作方式了

276 Chapter 6 衍生類別與繼承 Animal 類別需要作一點修改 要使衍生類別物件可以不斷地改變所選擇的 sound() 函 式, 此函式必須是基礎類別的成員 對於 Animal 類別, 我們可以增加一個無內容限 制的 sound() 函式, 其內容如下 : class Animal { // Rest of the class as before... // Dummy method to be implemented in the derived classes public void sound(){ 只有某種個別的 Animal 物件會使用到代表聲音的函式, 所以在此類別中,sound() 函式不作任何事情 在此需要一個使用這些類別的程式 為了測試這些類別, 建立一個 Animal 型態的陣列, 並將不同子類別的物件移入元素中 之後, 由陣列隨機選取物件, 在選取物件之前, 不可能知道被選取的物件是何種型態 以下程式是用來測試上述的新類別, 其內容如下 : import java.util.random; public class TryPolymorphism { public static void main(string[] args) { // Create an array of three different animals Animal[] theanimals = { new Dog("Rover", "Poodle"), new Cat("Max", "Abyssinian"), new Duck("Daffy","Aylesbury") ; Animal petchoice; // Choice of pet Random select = new Random(); // Random number generator // Make five random choices of pet for(int i = 0; i < 5; i++) { // Choose a random animal as a pet petchoice = theanimals[select.nextint(theanimals.length)]; System.out.println("\nYour choice:\n" + petchoice); petchoice.sound(); // Get the pet's reaction 執行此程式, 其輸出結果如下 : Your choice: This is a Duck It's Daffy the Aylesbury Quack quackquack Your choice: This is a Cat It's Max the Abyssinian Miiaooww Your choice: This is a Duck

多重階層的繼承 277 It's Daffy the Aylesbury Quack quackquack Your choice: This is a Duck It's Daffy the Aylesbury Quack quackquack Your choice: This is a Cat It's Max the Abyssinian Miiaooww 和先前不同的地方在於 這是一個不同的輸出集合, 且每次重新執行此範例, 其結果都不相同 由此範例的輸出可知, 函式是在執行的過程中被選取出來的, 而選取的方式是以 petchoice 變數所儲存的物件來決定的 程式如何運作在 Animal 類別裡,sound() 函式的定義中並沒有任何敘述, 所以, 當它執行時, 並不會作任何事情 本章稍後將看到如何避免讓函式包含空的定義, 不過仍可在衍生類別裡達到同名異式的功能 在此需要 import 敘述, 就和以前的範例一樣, 範例中使用了 Random 類別來產生隨機的索引值 Animal 型態的 theanimals 陣列包含了一個 Dog 物件 一個 Cat 物件和一個 Duck 物件 我們可在 for 迴圈裡使用 Random 物件隨機地挑選陣列裡的物件, 並將選擇的結果儲存在 petchoice 裡 之後, 使用儲存之物件的 reference, 呼叫 tostring() 函式以及 sound() 函式 這樣做的效果是 適當的函式會自動地被選取, 以適應被儲存的物件, 所以, 程式會依照 petchoice 所參考之物件型態的不同, 執行不同的函式內容 當然, 呼叫 tostring() 函式的動作包含在 println() 的引數裡, 編譯程式會插入一個 tostring() 的呼叫, 以 String 表示 petchoice 物件 個別的 tostring() 函式會根據 petchoice 所參考的物件型態而自動地被選取出來, 這種方式即使是基礎類別並沒有撰寫 tostring() 函式, 仍然可以使用 無論是否定義 tostring() 函式, 由本章後面的內容中, 我們將會知道 每個類別都有 tostring() 函式 同名異式是物件導向程式設計的基本觀念, 在本書後面的範例裡, 將會有更多關於同名異式延伸的使用, 此外, 往後您將發現您的應用程式及 applets 都會經常地使用它 Java 的同名異式的觀念尚未完全討論完畢, 本章稍後會再次回到這個主題 多重階層的繼承 如同本章一開始所表示的, 當衍生類別要作為基礎類別時, 並不需要預防什麼 例如, 從 Dog 類別衍生出另一個 Spaniel 類別, 通常並不會發生任何問題

278 Chapter 6 衍生類別與繼承 Spaniel 類別 先由最少的程式碼開始撰寫 Spaniel 類別, 其內容如下 : class Spaniel extends Dog { public Spaniel(String aname) { super(aname, "Spaniel"); 若要測試此類別, 可以改變前一個範例的敘述, 將一個 Spaniel 物件加入 theanimals 陣列, 其內容如下 : Animal[] theanimals = { new Dog("Rover", "Poodle"), new Cat("Max", "Abyssinian"), new Duck("Daffy","Aylesbury"), new Spaniel("Fido") ; 請別忘了在 Duck 後面加一個逗號 試著將範例再執行一次 程式如何運作 Spaniel 類別會繼承 Dog 類別的成員, 其中包括 Dog 由 Animal 類別繼承而來的成員 Dog 類別是 Spaniel 類別的直系父類別 ; 而 Animal 類別則是 Spaniel 類別的間接父類別 Spaniel 唯一增加的成員只有建構式, 它會使用 super 關鍵字去呼叫 Dog 類別的建構式, 並將 aname 的數值和 String 物件 "Spaniel" 傳遞給 Dog 的建構式 如果再次執行 TryPolymorphism 類別, 程式應該會經常選到 Spaniel 物件 因為 Spaniel 類別會繼承其父類別 Dog, 因此也會加入 tostring() 以及 sound() 的同名異式選取行列中 Spaniel 物件繼承而來的 tostring() 函式, 可以非常順利地使用, 不過若想要提供較為獨特的 tostring() 版本, 可在 Spaniel 類別定義另外加上一個 tostring() 函式 如此一來, 以後選取 tostring() 函式時, 將自動地從 Spaniel 物件裡選取, 而不會使用由 Dog 類別繼承而來的 tostring() 函式 Abstract 類別 在 Animal 類別裡, 我們介紹了一個不作任何事的 sound() 函式, 因為我們希望 sound() 函式可以在子類別的物件裡作不同的變化 sound() 函式在 Animal 類別裡並沒有什麼特別的意義, 所以在 Animal 裡實作此函式的程式沒有太大的意義 同樣的情形在物件導向程式設計裡經常發生, 您會經常發現, 許多為了衍生子類別而建立的父類別, 只是為了使用同名異式的好處而已 因此,Java 提供了 abstract 類別 (abstract class) abstract 類別是指 在一個類別裡宣告了一個或多個函式, 但卻沒有定義 這些函式的主體會被忽略, 就像

Abstract 類別 279 Animal 類別的 sound() 函式一樣, 在那裡實作它們是沒有意義的 也因為它們沒也定義, 而且無法執行, 因此, 這些函式被稱為 abstract 函式 (abstract method) abstract 函式的宣告會以分號作結尾, 並使用 abstract 關鍵字來辨別它 若要定義 abstract 類別, 可在類別定義的第一行中,class 關鍵字前面使用 abstract 關鍵字 將原本 Animal 類別定義修改為 abstract 類別, 其內容如下 : public abstract class Animal { public abstract void sound(); // Abstract method public Animal(String atype) { type = new String(aType); public String tostring() { return "This is a " + type; private String type; 作完這些改變以後, 前一個程式仍可順利執行 無論類別名稱前使用 public abstract 或 abstract public, 其效果都相同, 不過您本身的使用習慣應該保持一致 一般來說,public abstract 的順序是比較好的 同樣的方式也可用在 abstract 函式的宣告中, 不過 public 和 abstract 必須在回傳值型態之前, 就以上面的例子而言, 其回傳值型態就是 void 一個 abstract 函式不可以為 private, 因為 private 無法被繼承, 因此無法在子類別中重新定義 雖然不可以建立 abstract 類別的物件, 不過卻可以宣告 abstract 類別型態的變數 以新版本的 Animal 類別來說, 您可以這樣宣告 : Animal thepet = null; // Declare a variable of type Animal 如同 TryPolymorphism 類別所做的, 如此一來, 即可使用此變數來儲存 Animal 的子類別 Dog Spaniel Duck 和 Cat 的物件 從 abstract 類別衍生出一個類別時, 不需在子類別裡定義所有的 abstract 函式 在這種情況下, 子類別也會是 abstract, 且無法建立該子類別的任何物件 如果類別是 abstract, 就必須使用 abstract 關鍵字來定義它, 即使只有一個由其父類別繼承而來的 abstract 函式 ; 不過早晚會有不包含任何 abstract 函式的的子類別 接下來即可建立此種類別型態的物件