194
本本本本 本本本本本本本 本本本本本本本本本本本本本本本本本本本本本本本本本本本 本本本本本 本本本 本本本本本本本本本本本 本本本本 本本本 本本本本本本本 本本 ()(),,、、( 本本本本本本本本本本本本本本本 80% 本本本本本本本本本本本本本本本 本本本本本本本本本本本本本本本本 本本本本本本本本本本本本本本本本本本 本本本本本本本本本 ),,、; 本本本本 本本本本本本本本本本本本本本本本 本本本本本本本本本本本本本本本本本本本本本本本本本本本本 ,,。 本本本本本 © 本本本本本本本本本本 本本 (Inheritance) 本 11 本

SCJP ch11

Embed Size (px)

Citation preview

Page 1: SCJP ch11

本投影片(下稱教用資源)僅授權給採用教用資源相關之旗標書籍為教科書之授課老師(下稱老師)專用,老師為教學使用之目的,得摘錄、編輯、重製教用資源(但使用量不得超過各該教用資源內容之 80% )以製作為輔助教學之教學投影片,並於授課時搭配旗標書籍公開播放,但不得為網際網路公開傳輸之遠距教學、網路教學等之使用;除此之外,老師不得再授權予任何第三人使用,並不得將依此授權所製作之教學投影片之相關著作物移作他用。

著作權所有 © 旗標出版股份有限公司

繼承 (Inheritance)

第 11 章

Page 2: SCJP ch11

2

學習目標

認識繼承關係 學習建構類別的關係 認識多形 (Polymorphism)

認識 Object 類別

Page 3: SCJP ch11

3

前言

前兩章介紹了設計類別的基本方式 , 但當我們要設計功能較複雜的類別時 , 會發現每次要從無到有 , 設計出類別的所有功能 , 有時是很累人的一件事。

如果已有人設計出功能類似的其它類別 , 而我們可直接將它擴充使用 , 可讓開發程式所需的時間大幅縮短。

Page 4: SCJP ch11

4

前言

或者當所設計的程式含有多個類別時 , 有時您會發有某些類別彼此間會有許多共通的地方 , 例如有共通的成員變數、建構方法很相似等等。

如果能有一種方式 , 可以將這些相似的地方表達出來 ,

而不需要不同類別中重複描述 , 就可以讓各個類別只專注於其所特殊的地方 , 整個程式也會更加簡潔了。

Page 5: SCJP ch11

5

前言

在這一章中所要介紹的就是解決上述問題的機制 --

繼承 , 還記得在第 8 章時我們以舞台劇來比擬 Jav

a 程式。 以物件導向的方式設計 Java 程式時 , 第一件事情就是要分析出程式中所需的各種物件 ( 也就是舞台劇的角色 ) , 而本章的內容就是要提供一種技巧 , 可以讓我們以系統化的方式描述相似但不相同的物件。

Page 6: SCJP ch11

6

11-1 甚麼是繼承?

簡單的說 , 繼承就是讓我們可沿用已經設計好的類別 ,

替它擴充功能以符合新的需求 , 定義出一個與舊類別相似 , 但具有新方法與新屬性的類別。

透過這種方式 , 將可大幅提高程式可重複使用的特性 ,

因為程式開發者 , 可藉由繼承的方式 , 讓既有的類別 , 能順利應用於新開發的程式 , 節省開發重複程式的時間。

此外藉由繼承的架構 , 我們可將不同的類別依據其相似程度 , 整理成一個體系 , 讓我們所開發的程式更加模組化。

Page 7: SCJP ch11

7

11-1-1 不同物件的相似性

舉例來說 , 如果圓形類別可以用圓心座標和半徑這兩個屬性來描述:

Page 8: SCJP ch11

8

不同物件的相似性

Page 9: SCJP ch11

9

不同物件的相似性

假如圓柱體的描述方式是用底部的圓 , 再加上圓柱的高度來描述 , 則圓柱體類別可定義成:

Page 10: SCJP ch11

10

不同物件的相似性

Page 11: SCJP ch11

11

不同物件的相似性

我們可以發現 , 這兩個類別有很多相似之處 , 而且 Circle 類別所包含的也都是 Cylinder 類別會出現的部份。

如果將兩個類別分別撰寫 , 相同的成員變數以及建構方法必須在這兩個類別中重複出現 , 而且往後需要修改時 , 還必須分別到兩個類別中修改 , 不但費事 , 也可能因為修改的不一致而導致錯誤。

Page 12: SCJP ch11

12

不同物件的相似性

很顯然的 , Cylinder 類別算是 Circle 類別的延伸 ,

因此在已事先設計好 Circle 類別的情況下 , 即可利用繼承的方式 , 讓 Cylinder 類別繼承 Circle 類別 ,

我們僅需定義 Cylinder 類別與 Circle 類別不同的部份 , 而不需重複定義兩者相同的屬性及方法 , 即可完成 Cylinder 類別的定義。

Page 13: SCJP ch11

13

11-1-2 繼承的語法

要繼承現有的類別 , 需使用 extends 關鍵字 , 語法如下:

例如:

Page 14: SCJP ch11

14

繼承的語法

其中 Circle 稱為父類別 (Parent Class 或 Super

class), 而 Cylinder 則稱為子類別 (Child Class

或 Subclass) 或是衍生類別 (Extended Class),

有時我們也稱這個動作為:『從 Circle 類別衍生 Cyclinder 類別』。

子類別將會繼承父類別的所有成員變數和方法 , 所以子類別的物件可直接使用從父類別繼承而來的成員變數和方法 , 以下我們將兩個類的內容先簡化一下 , 並來看繼承的效果。

Page 15: SCJP ch11

15

繼承的語法

Page 16: SCJP ch11

16

繼承的語法

Page 17: SCJP ch11

17

繼承的語法

第 19 行定義 Cyclinder 繼承自 Circle 類別 , 且未定義任何成員變數及方法 , 但因父類別的成員變數和方法都會繼承給子類別 , 所以實際上 Cylinder 類別也具有成員變數 x 、 y 、 r, 不過這些成員變數在 Circle 中被宣告為 private, 所以 Cylinder 不能直接存取它們。

但可呼叫 public 的 setCenter() 、 setRadius() 、 t

oString() 等方法。

Page 18: SCJP ch11

18

繼承的語法

所以在第 26 行建立 Cylinder 類別的物件 cr 後 , 即可透過此物件呼叫上述方法。

而執行結果即是 Circle 類別的 toSt

ring() 方法 , 將成員變數轉成字串的結果 , 因此只輸出繼承自父類別的部分 , 而沒有輸出圓柱的高度。

Page 19: SCJP ch11

19

11-1-3 繼承關係中的物件建構

前面由 Circle 衍生的 Cylinder 類別中 , 並未定義自己的成員變數與方法 , 所有內容都是由父類別 Cir

cle 繼承而來 , 接下來即要替它加入屬於自己的部份。 加入自己的成員變數時 , 當然要用建構方法來進行初始化 , 這時候在衍生類別可以只初始化自己的成員變數 , 繼承來的成員變數可交由父類別的建構方法做初始化 , 這是因為建立衍生類別的物件時 , Java 會自動呼叫父類別的建構方法。

我們可由以下的範例 , 觀察父類別建構方法是如何被呼叫的。

Page 20: SCJP ch11

20

繼承關係中的物件建構

Page 21: SCJP ch11

21

繼承關係中的物件建構

Page 22: SCJP ch11

22

繼承關係中的物件建構

Page 23: SCJP ch11

23

繼承關係中的物件建構

由執行結果可以發現 , 當程式建立 Cylinder 物件 c

r, 在呼叫 Cylinder() 建構方法之前 , 即已自動呼叫 Circle() 建構方法。

道理很簡單 , 因為 Cylinder 繼承了 Circle 類別的內容 , 因此要產生其物件時 , 當然要先初始化繼承而來的部份 , 所以會先呼叫父類別的建構方法。

Page 24: SCJP ch11

24

繼承關係中的物件建構

當然我們也可以在子類別的建構方法中 , 初始化繼承而來的 public 、 protected 成員變數。

舉例來說 , 若上一個範例中 Circle 類別的成員變數未宣告成 private, 則 Cylinder 類別就可以有如下的建構方法:

Page 25: SCJP ch11

25

呼叫父類別的建構方法 但如果在 Circle 類別中已有建構方法可進行各成員變數的初始化 , 那麼在子類別建構方法中又重新建構一次 , 不是又回到未使用繼承之間 , 程式碼重複出現的情況?

要避免此情形 , 當然就是將繼承來的成員變數都交給父類別處理 , 子類別只需初始化自己的成員變數。

然而當建構子類別的物件時 , new 後面接的是它自己的建構方法 ( 例如 Cylinder cr=new Cylinder() ; ), 換言之參數只能傳給子類別的建構方法 , 因此我們必須在子類別建構方法中 , 再用所接收的參數呼叫父類別的建構方法 , 間接初始化繼承自父類別的成員變數。

Page 26: SCJP ch11

26

呼叫父類別的建構方法

要呼叫父類別的建構方法 , 不能直接用父類別的名稱來呼叫 , 而是必須使用 super 保留字。

super 代表的就是父類別 , 當 Java 看到 super 保留字時 , 即會依所傳遞的參數型別和數量 , 呼叫父類別中對應的建構方法。

因此當我們為前述的 Circle 類別加上建構方法後 ,

即可依如下的方式在 Cylinder 類別中呼叫它。

Page 27: SCJP ch11

27

呼叫父類別的建構方法

Page 28: SCJP ch11

28

呼叫父類別的建構方法

Page 29: SCJP ch11

29

呼叫父類別的建構方法

Page 30: SCJP ch11

30

呼叫父類別的建構方法 第 14 行的 Circle() 建構方法使用 3 個參數來初始化圓心及半徑 , 在第 28〜 31 行的 Cylinder 建構方法則有 4 個參數 , 並以前 3 個參數於第 29 行用 super() 的方式呼叫父類別的建構方法 , 另一個參數則用於設定高度的初值。

由執行結果也可看到圓心及半徑確實有成功初始化為指定的值。

透過 super() 來呼叫父類別建構方法 , 也有另一個好處:當父類別的實作方式有所變更時 , 我們也不需修改子類別的程式 , 充份發揮物件導向程式設計的方便性。

Page 31: SCJP ch11

31

呼叫父類別的建構方法▪ 請注意 , 呼叫 super() 的敘述必須寫在建構方法的最前面 , 也就是必須先呼叫 super(), 然後才能執行其他敘述。

▪ 在建構子類別的物件時 , 一定會先由下往上呼叫其各層父類別的建構方法。如果建構方法最前面沒有 super(), 也沒有 this() (呼叫目前類別的其他建構方法 ), 那麼 Java 編譯器會自動加入一個無參數的 super() 。

Page 32: SCJP ch11

32

11-1-4 再論資訊隱藏

如果不想使用 super(), 而要讓子類別建構方法自行初始化繼承而來的成員變數 , 就要注意父類別成員變數的存取控制設定。

在前面的範例中 , Circle 的成員變數都宣告為 privat

e, 因此其子類別 Cylinder 根本無法存取它們 , 也不能自行在建構方法中初始化。

然而若將 Circle 的成員變數宣告為 public, 卻又讓 Circle 類別失去封裝與資訊隱藏的效果。

Page 33: SCJP ch11

33

再論資訊隱藏

在第 9 章討論資訊隱藏時 , 曾提到成員變數可以加上 public 、 protected 、或是 private 其中一種存取控制字符 , 當時所略過的 protected 就可以解決上述的問題 , 因為它是介於 private 和 public 之間的存取控制 , protected 代表的是只有子類別或是同一套件 (Package, 詳見第 13 章 ) 的類別才能存取此成員。

另外 , 如果將這些字符都省略 , 那麼就只限同一套件中的類別才能存取。

Page 34: SCJP ch11

34

再論資訊隱藏

以 Circle 類別為例 , 若我們希望將其成員變數設為子類別可存取 , 即可將它們宣告為 protected 。

經過這樣的修改 , 子類別建構方法就能自行初始化繼承而來的成員變數了 , 例如以下的例子。

Page 35: SCJP ch11

35

再論資訊隱藏

Page 36: SCJP ch11

36

再論資訊隱藏

Page 37: SCJP ch11

37

再論資訊隱藏

在 Circle 類別中 , 除了於第 3 、 4 行將成員變數改成使用 protected 存取控制外 , 也特意將其建構方法去除。

而衍生的 Cylinder 類別 , 則於第 23〜 28 行的建構方法中 , 初始化繼承來的 x 、 y 、 r , 及自己獨有的 h , 由於父類別的成員變數是 protected, 所以可在子類別中直接存取之。

Page 38: SCJP ch11

38

11-1- 5 多層的繼承 (Hierarchical Inheritance)

衍生而來的子類別 , 也可以再當成父類別以衍生出其它的子類別 , 形成多層的繼承關係 , 此時最下層的衍生類別 , 會繼承到其繼承關係上所有父類別成員變數及方法 , 這些特性其實和單層的繼承是相同的。

舉例來說 , 當我們要設計的圖形類別種類更多時 , 發現很多圖形都要記錄座標點。

因此為了簡化設計 , 我們可將 Circle 類別的圓心座標再抽取出來 , 形成另一個新的父類別 Shape 。

Page 39: SCJP ch11

39

多層的繼承 (Hierarchical Inheritance)

然後讓 Circle 類別及其它圖形類別共同繼承這個新的類別 , 而 Cylinder 類別就變成此繼承關係中的最下層類別 , 它將繼承到 Shape 及 Circle 類別的內容。

Page 40: SCJP ch11

40

多層的繼承 (Hierarchical Inheritance)

透過這樣的規劃 , 就能讓每個類別都只描述該類別特有的部份 , 共通的部份都是由父類別繼承而來。

以下就是這個三層繼承架構的範例 , 其中 Circle 類別的內容較先前的範例略為簡化 , 以清楚示範繼承的關係。

Page 41: SCJP ch11

41

多層的繼承 (Hierarchical Inheritance)

Page 42: SCJP ch11

42

多層的繼承 (Hierarchical Inheritance)

Page 43: SCJP ch11

43

多層的繼承 (Hierarchical Inheritance)

Page 44: SCJP ch11

44

多層的繼承 (Hierarchical Inheritance)

Page 45: SCJP ch11

45

多層的繼承 (Hierarchical Inheritance)

第 14 、 24 行的 Rectangle 、 Circle 類別定義都使用了 extends 語法繼承 Shape 類別 , 而第 33

行的 Cylinder 類別則是繼承 Circle, 所以 Cylinde

r 除了會有 Circle 的成員變數 r, 也會間接繼承到 Shape 的成員變數 x 、 y 。

在第 18 、 28 、 37 行都用 super() 呼叫父類別的建構方法 , 但要注意前 2 者是呼叫 Shape() 建構方法 , 第 37 行則是呼叫到 Circle 類別的建構方法。

Page 46: SCJP ch11

46

11-2 多形 (Polymorphism) – 方法的重新定義 (Overriding)

在上一節中 , 已經簡單的介紹了繼承的觀念 , 在這一節中 , 要介紹讓繼承發揮最大效用所必須仰賴的機制 -- 方法的繼承 , 以及伴隨而來的多形。

Page 47: SCJP ch11

47

11-2-1 方法的繼承

如同前一節使用父類別的成員變數一樣 , 父類別中的方法也一樣會由子類別所繼承。

換句話說 , 父類別中所定義的所有方法 , 就等於是在子類別中也有定義一樣 , 除非在父類別中有加上特定的存取控制 , 否則在子類別中以及子類別的使用者就可以呼叫這些方法 , 就像是這些方法是由子類別所定 ,

例如。

Page 48: SCJP ch11

48

方法的繼承

Page 49: SCJP ch11

49

方法的繼承

在第 13 行中就呼叫了 Show() 方法 , 可是在 Chi

ld 類別中並沒有定義 Show() 方法 , 在編譯時 Jav

a 編譯器會循著繼承的結構 , 往父類別尋找 , 發現父類別所定義的 Show() 方法。

如果有多層的繼承結構 , 就會一路往上找 , 直到找到為止。

如果父代類別中都沒有 , 就會產生編譯錯誤。

Page 50: SCJP ch11

50

11-2-2 方法的重新定義 (Overridding)

如果父類別所定義的方法不符使用 , 那麼也可以在子類別中重新定義該方法 , 這稱為 Overriding 。例如:

Page 51: SCJP ch11

51

方法的重新定義 (Overridding)

Page 52: SCJP ch11

52

方法的重新定義 (Overridding)

由於 Child 類別中重新定義了父類別的 Show() 方法 , 所以在第 16 行中呼叫 Show() 方法時 , 執行的就是子類別中的 Show() 方法。

這就相當於在 Child 類別中用新定義的 Show() 方法把父類別中定義的同名方法給蓋住了 , 所以對於 Child 物件來說 , 只看得到 Child 類別中的 Show

() 方法 , 因此呼叫的就是這個方法。

Page 53: SCJP ch11

53

方法的重新定義 (Overridding)

請注意 , 重新定義父類別的方法時 , 其傳回值型別必須和原來的一樣才行;但有一個例外 , 就是原傳回值型別可改成其子類別 , 例如可將父類別的 Parent g

etMe() 方法重新定義為 Child getMe(), 而這種傳回子類別的方式就

稱為 Covariant return (子代父還 ), 只有在 Java

5.0 以上的版本才有支援。

Page 54: SCJP ch11

54

11-2-3 多重定義父類別的方法 您也可以在子類別中使用多重定義 (Overloading) 的方式 , 定義和父類別中同名 , 但參數個數或是型別不同的方法:

Page 55: SCJP ch11

55

多重定義父類別的方法

Page 56: SCJP ch11

56

多重定義父類別的方法

當第 16 行呼叫不需參數的 Show() 方法時 , 由於 Child 類別並沒有符合的方法 , 因此會往父類別找 ,

所以呼叫的就是 Parent 類別的 Show() 方法。 但是在第 17 行呼叫傳入 String 的 Show() 方法時 , 由於 Child 類別本身就定義有相符的方法 , 所以呼叫的就是 Child 中的 Show() 方法。

Page 57: SCJP ch11

57

多重定義父類別的方法

簡單來說 , 當呼叫方法時 , Java 編譯器會先在類別本身找尋是否有名稱、參數個數與型別皆相符的方法。

如果有 , 就採用此方法;如果沒有 , 就依循繼承結構 ,

往上層類別尋找。▪ 在多重定義 (Overloading) 父類別的方法時 , 其傳回值的型別可任意更改 , 這點和重新定義 (Overridding)

不同。

Page 58: SCJP ch11

58

11-2-4 多形 (Polymorphism)

方法的繼承要能真正發揮威力 , 必須仰賴多形。 前面曾經提過 , 子類別是延伸父類別而來 , 亦即子類別的內容包含了父類別所有的內容。

因此 , 以剛剛 Overridding.java 為例 , 我們可以說 c 所指向的是一個 Child 物件 , 也可以說 c 所指向的是一個 Parent 物件。

這就好像我們可以說:『張三是「人」 , 但也是「哺乳動物」』。

Page 59: SCJP ch11

59

多形的意義

由於越下層的類別一定包含了上層的成員變數 , 因此 ,

在 Java 中 , 您可以使用上層類別的參照去指向一個下層類別的物件 , 例如:

Page 60: SCJP ch11

60

多形的意義

Page 61: SCJP ch11

61

多形的意義

在第 24 行中 , p 原本是個指向 Parent 物件的參照 , 但因為 Child 是 Parent 的子類別 , 而一個 Child 物件一定也包含了 Parent 物件的所有內容 ,

因此任何需要 Parent 物件的場合 , 都可以 Child

物件來替代。 所以在這一行中 , 就將 p 指向一個 Child 物件。 接著 , 在第 25 行中 , 就呼叫了 Show() 方法。 因為 p 實際指向的是一個 Child 物件 , 所以呼叫的是 Child 類別中所定義的 Show() 方法 , 而不是 Parent 類別中所定義的 Show() 方法。

Page 62: SCJP ch11

62

多形的意義

這是因為 Java 會依據參照所指物件的型別來決定要呼叫的方法版本 , 而不是依參照本身的型別來決定。

以剛剛的例子來說 , 雖然 p 是一個 Parent 型別的參照 , 但是執行到第 25 行時 , p 所指的物件是屬於 Child 類別 , 因此真正呼叫的方法會是 Child 類別中所定義的 Show() 方法。

如果 Child 類別中沒有參數數量與型別相符的方法 ,

才會呼叫從父類別中繼承而來的同名方法。

Page 63: SCJP ch11

63

多形的意義

Page 64: SCJP ch11

64

多形的意義

Page 65: SCJP ch11

65

多形的意義

在第 21 行呼叫 Show() 方法時 , 呼叫的是不需參數的版本 , 此時雖然 p 是指向 Child 物件 , 但是因為 Child 類別中並沒有定義不需參數的版本 , 所以實際呼叫的就是繼承自 Parent 類別的 Show()

方法。 像這樣透過父類別的參照 , 依據實際指向物件決定呼叫方法的機制就稱為『多形』 , 表示雖然參照的型別是父類別 , 但實際指向的物件卻可能是父類別的物件或是其任何子類別的物件 , 因此展現的行為具有多種形貌的意思。

Page 66: SCJP ch11

66

編譯時期的檢查 要特別注意的是 , 在編譯時 Java 編譯器會先依據參照的型別來檢查是否可以呼叫指定的方法 , 而實際呼叫的動作則是等到程式執行的時候才依據參照指向的物件所屬的類別來決定。

也就是說 , 參照的型別決定了可以呼叫哪些方法 , 而參照指向的物件決定了要呼叫哪一個版本的方法。

例如將前面程式第 21 行改為『 p.show(" 這樣可以嗎? ");』 , 那麼就會編譯錯誤:

Page 67: SCJP ch11

67

編譯時期的檢查

由於 p 是 Parent 型別的參照 , 而 Parent 類別中並沒有定義傳入一個 String 資料的 Show() 方法 ,

所以在第 21 行嘗試透過 p 呼叫 Show( "..." ) 方法時 , Java 編譯器就會發現 Parent 類別中沒有相符的方法而發生錯誤。

Page 68: SCJP ch11

68

強制轉型 如果您確信某個父類別的參照實際上所指向的是子類別的物件 , 那麼也可以透過強制轉型的方式 , 將參照值指定給一個子類別的參照 , 例如:

Page 69: SCJP ch11

69

強制轉型

Page 70: SCJP ch11

70

強制轉型

1.第 21 行中使用了 Java 所提供的 instanceof 運算子 , 可以判定左邊運算元所指向的物件是否屬於右邊的類別 , 並得到布林值的運算結果。

2.第 22 行中就在 p 是指向 Child 物件的情況下 ,

使用強制轉型的方式 , 把 p 的參照值指定給 Child

類別的參照 aChild, 並且在第 23 行呼叫 Child

類別的 Show() 方法。 在此特別提醒讀者 , 強制轉型是危險的動作 , 如果參照所指的物件和強制轉型的類別不符 , 就會在執行時期發生型別轉換錯誤。

Page 71: SCJP ch11

71

多形的規則

現在讓我們整理呼叫方法時的規則:1. Java 編譯器先找出參照變數所屬的類別。

2. 檢查參照變數所屬類別中是否有名稱相同 , 而且參數個數與型別皆相同的方法。如果沒有 , 就會產生編譯錯誤。

3. 執行程式時 , Java 虛擬機器會依循參照所指向的物件 , 呼叫該物件的方法。

▪ 請務必瞭解上述規則。

Page 72: SCJP ch11

72

11-2-5 多形的效用

多形要真正發揮效用 , 多半是與參數的傳遞有關。 舉例來說 , 假設要撰寫一個計算地價的程式 , 那麼可能會使用不同的類別來代表各種形狀的土地 ( 當然 ,

實際的土地不會這麼規則 ) :

Page 73: SCJP ch11

73

多形的效用

Page 74: SCJP ch11

74

多形的效用

接下來我們想要撰寫一個 Calculator 類別 , 它擁有一個成員變數 price, 記錄了目前每平方公尺面積的地價 , 並且提供一個 calculatePrice() 方法 , 可以傳入代表土地的物件 , 然後傳回該塊土地的地價。

Page 75: SCJP ch11

75

使用多重定義處理不同類別的物件

由於 Circle 及 Square 是兩種不同的物件 , 因此解決方案之一就是在 Calculator 類別中使用多重定義的 calculatePrice() 方法計算不同形狀土地的價值:

Page 76: SCJP ch11

76

使用多重定義處理不同類別的物件

Page 77: SCJP ch11

77

使用多重定義處理不同類別的物件

在這個類別中 , 就提供了 2 種版本的 calculatePri

ce() 方法 , 分別計算圓形以及正方形土地的地價。有了這個類別後 , 就可以實際計算地價了:

Page 78: SCJP ch11

78

使用多重定義處理不同類別的物件

您可以看到 , 這個程式雖然可以正確執行 , 但卻有一個重大的問題。

由於使用了多重定義的方法 , 代表著如果有另外一種土地形狀的新類別時 , 就必須修改 Calculator 類別 , 新增一個對應到新類別的 calculatePrice() 方法。

往後只要有建立代表土地形狀的新類別 , 就得持續不斷地修改 Calculator 類別。

Page 79: SCJP ch11

79

使用多形讓程式具有彈性

要解決這個問題 , 可以使用繼承以及多形的技巧 , 讓代表不同形狀土地的類別都繼承自同一個父類別 , 然後在 calculatePrice() 方法中透過多形的方式呼叫不同類別的 area() 方法:

Page 80: SCJP ch11

80

使用多形讓程式具有彈性

Page 81: SCJP ch11

81

使用多形讓程式具有彈性

Page 82: SCJP ch11

82

使用多形讓程式具有彈性

Page 83: SCJP ch11

83

使用多形讓程式具有彈性

在這個程式中 , 新增了 Land 類別 , 作為 Circle

以及 Square 類別的父類別。 這樣一來 , calculatePrice() 方法就可以改成傳入一個

Land 物件 , 並在執行時依據所指向的物件呼叫對應的 area() 方法了。

以後即便有新土地形狀的類別 , 也不需要修改 Calc

ulator 類別。

Page 84: SCJP ch11

84

11-3 繼承的注意事項

到目前為止 , 已經將繼承的大部分概念介紹完畢 , 在這一節中 , 要針對實際使用繼承時所必須注意的事項 ,

一一提出來 , 避免撰寫出錯誤的程式。

Page 85: SCJP ch11

85

11-3-1 繼承與存取控制

在 11-1-4 中已經提過 , 您可以使用存取控制字符來限制父類別的成員變數或方法是否能夠在子類別中或是類別以外的地方使用。

對於這樣的限制 , 您可以在重新定義方法時同時修改 ,

但是要注意的是子類別中只能放鬆限制 , 而不能讓限制更加嚴格 , 例如。

Page 86: SCJP ch11

86

繼承與存取控制

Page 87: SCJP ch11

87

繼承與存取控制

Page 88: SCJP ch11

88

繼承與存取控制

當嘗試將 Child 類別中的 Show() 方法限制為更嚴格的 private 時 , 就會發生編譯錯誤。

原因很簡單的 , 如果將子類別中的方法重新定義為更嚴格 , 那麼原本可以呼叫父類別中此方法的場合就會因為存取限制更加嚴格而無法呼叫 , 導致本來可以執行的程式發生錯誤了。▪ 請留意這些限制 , 有時程式無法編譯就導源於這些小地方。

Page 89: SCJP ch11

89

繼承與存取控制

舉例來說 , 第 17 行的 p 是一個 Parent 類別的參照。依據 Parent 的定義 , 是可以呼叫 public 存取控制的 Show() 方法 , 如果允許將 Child 類別中的 Show() 方法改成更嚴的 private 存取控制 , 那就會導致第 17 行不能執行 , 因為 p 實際所指的是 Child 物件。

因此 , 在重新定義方法時 , 並不允許您將存取控制更改為更嚴格。

Page 90: SCJP ch11

90

繼承與存取控制

以下是不同存取控制的說明及嚴格程度 (最下方的 public 是最鬆的存取控制 ) :

Page 91: SCJP ch11

91

11-3-2 定義同名的成員變數

子類別中不僅可以重新定義父類別中的方法 , 而且還可以重新定義父類別中的成員變數。例如:

Page 92: SCJP ch11

92

定義同名的成員變數

Page 93: SCJP ch11

93

定義同名的成員變數

重新定義的成員變數和父類別中的同名成員變數是獨立的 , 也就是說 , 在子類別中其實是擁有 2 個同名的成員變數。

不過由於同名所產生的名稱遮蔽 (Shadowing) 效應 ,

當在子類別中引用成員變數時 , 使用到的就是子類別中所定義的成員變數 , 因此第 14 行顯示成員變數 i 時 , 顯示的就是 Child 類別中的成員變數。

Page 94: SCJP ch11

94

透過 super 使用父類別的成員變數

如果需要使用的是父類別的成員變數 , 那麼可以在子類別中使用 super 保留字 , 或者是透過父類別的參照。例如:

Page 95: SCJP ch11

95

透過 super 使用父類別的成員變數

Page 96: SCJP ch11

96

透過 super 使用父類別的成員變數

第 9 行就是在子類別中透過 super 保留字存取父類別中的同名成員變數;而第 19 行則是因為 Java

編譯器看到 p 是 Parent 型別的參照 , 因此存取的就是 Parent 類別中所定義的成員變數。

Page 97: SCJP ch11

97

透過 super 呼叫父類別的方法

super 除了可以用來存取父類別中被遮蔽的同名成員變數外 , 也可以用來呼叫父類別中被子類別重新定義或者是多重定義的同名方法 , 例如:

Page 98: SCJP ch11

98

透過 super 呼叫父類別的方法

Page 99: SCJP ch11

99

透過 super 呼叫父類別的方法 相同的技巧也可以應用在 static 成員變數上 , 例如:

Page 100: SCJP ch11

100

透過 super 呼叫父類別的方法

Page 101: SCJP ch11

101

透過 super 呼叫父類別的方法

這個結果顯示 , 凡是 Parent 以及其衍生類別的物件都共用了一份 static 的成員變數 i, 而凡是 Child

以及其衍生類別的物件都共用了另外一份 static 的成員變數 i, 這兩個同名的成員變數是完全獨立的。

因此 , 第 25 行透過 c2 更改成員變數 i 時 , 更改的是 Child 物件所共享的 i 。

當透過 c1 所指物件呼叫 Show() 時 , 可以看到 i

的值改變了 , 但是 super.i 取得的是 Parent 物件共享的 i, 並沒有改變。

Page 102: SCJP ch11

102

透過 super 呼叫父類別的方法

總結來說 , 存取哪一個成員變數是在編譯階段依據參照所屬的類別來確立的 , 只有方法的呼叫才是在執行時期依據參照所指的實際物件來決定。▪ 為了避免無謂的錯誤 , 我們並不建議您使用與父類別中同名的成員變數。但是重新定義父類別的方法卻是必要的 , 因為這可以搭配多形 , 撰寫出簡潔易懂的程式。

Page 103: SCJP ch11

103

11-3-3 不能被修改的方法 -- final 存取限制

有的時候我們會希望某個方法或成員變數可以讓子類別使用 , 但是不能讓子類別重新定義 , 這時便可以使用第 3 章介紹過的 final 字符 , 將方法或成員變數設定為不能再更改。例如:

Page 104: SCJP ch11

104

不能被修改的方法 -- final 存取限制

Page 105: SCJP ch11

105

不能被修改的方法 -- final 存取限制

第 12 行就因為 Parent 中已經將 Show() 設定為 final, 所以無法重新定義 , 編譯時會發生錯誤。▪ 之前已經提過 , 如果為成員變數加上 final 存取控制 ,

也就限制了該成員變數內容無法在設定初值後更改。

▪ 類別也可以加上 final, 表示不允許有子類別。例如 p

ublic final class A { }, 則 class B extends A { } 就會編譯錯誤 。

Page 106: SCJP ch11

106

11-3-4 建構方法不能被繼承 雖然子類別可以繼承父類別的方法 , 但是卻不能繼承父類別的建構方法 , 而只能呼叫父類別的建構方法。舉例來說:

Page 107: SCJP ch11

107

建構方法不能被繼承

Page 108: SCJP ch11

108

建構方法不能被繼承

雖然第 4 行 Parent 類別定義了需要一個整數的建構方法 , 但由於建構方法無法繼承 , 因此第 21 行想要透過傳入整數的建構方法建立 Child 物件時 , J

ava 編譯器會找不到 Chi ld 類別中相符的建構方法。

Page 109: SCJP ch11

109

建構方法不能被繼承

如果想要透過父類別的特定建構方法建立物件 , 必須在子類別的建構方法中以 super 明確呼叫:

Page 110: SCJP ch11

110

建構方法不能被繼承

Page 111: SCJP ch11

111

建構方法不能被繼承

第 21 行呼叫的是 Child 中不需參數的建構方法 ,

而在此建構方法中則透過 super 保留字呼叫父類別的建構方法。

再次提醒讀者 , 使用 super 呼叫父類別建構方法的動作必須出現在建構方法中的第一個敘述 , 否則編譯會發生錯誤。

Page 112: SCJP ch11

112

自動呼叫父類別的建構方法

如果沒有明確使用 super 呼叫父類別的建構方法 ,

那麼 Java 編譯器會自動幫您在建構方法的第一個敘述之前呼叫父類別不需參數的建構方法 , 例如:

Page 113: SCJP ch11

113

自動呼叫父類別的建構方法

Page 114: SCJP ch11

114

自動呼叫父類別的建構方法

從執行結果可以看到 , 雖然第 08 ~ 11 行子類別的建構方法中並沒有明確呼叫父類別的建構方法 , 但是父類別的建構方法會自動被呼叫 , 就好像第 09 行不是註解一樣。

Page 115: SCJP ch11

115

自動呼叫父類別的建構方法

由於 Java 預設呼叫的是不需任何參數的建構方法 ,

因此 , 除非您一定會使用 super 呼叫父類別的建構方法 , 或是在父類別中沒有定義任何建構方法 ( 此時編輯器會自動建一個空的建構方法 ) , 否則請務必幫父類別定義一個不需參數的建構方法 , 避免自動呼叫時發生錯誤。▪ 建構方法的呼叫也是重點 , 對於正確建構物件有很大的影響。

Page 116: SCJP ch11

116

11-3-5 類別間的 is-a 與 has-a 關係

is-a ( 是一種 ) 就是指類別之間的繼承關係 , 例如 C

ircle 繼承 Shape, 那麼 Circle 物件就是一種 (is-a)

Shape 。 而 has-a ( 有一種 ) 則是指類別間的包含關係 , 例如我們可先定義一個 Point (點 ) 類別 , 以做為 Circl

e 類別中的成員變數。

Page 117: SCJP ch11

117

類別間的 is-a 與 has-a 關係

Page 118: SCJP ch11

118

類別間的 is-a 與 has-a 關係

由於在 Circle 類別中宣告了 Point 成員變數 , 因此在 Circle 物件中有一種 (has-a) Point 物件。

換句話說 , 就是在 Circle 中有一個 Point 類別的物件參考。

在設計類別時 , is-a 和 has-a 的關係很容易被混洧 ,

例如本章一開頭所設計的『 Cylinder extends Circl

e』範例其實不正確 , 因為 Cylinder (圓柱體 ) 並不是一種 Circle (圓形 ), 而是包含圓形!

Page 119: SCJP ch11

119

類別間的 is-a 與 has-a 關係 所以應該改為 has-a 的關係會比較好:

那麼要如何分辨 is-a 與 has-a 的關係呢? 其實很簡單 , 只要將之翻成口語 , 就可以很直覺地判斷出來。例如汽車雖然擁有引擎的所有屬性 , 但汽車絕對不是一種引擎 , 而應該說汽車具備有 (has-a) 引擎。

Page 120: SCJP ch11

120

11-3- 6 鬆散耦合與高內聚力

耦合 (Coupling) 就是指類別與類別之間的關連程度。 基於類別封裝與資訊隱藏的概念 (參見第 9 章 ),

類別間的耦合當然是越低越好 , 也就是應保持所謂的鬆散耦合 (Loose coupling), 如此未來在維護 (修改或加強 ) 類別時才會越輕鬆。例如:

Page 121: SCJP ch11

121

鬆散耦合與高內聚力

以上在 Cylinder 的 getVolumn() 方法中 , 會直接讀取 Circle 物件的半徑 , 然後自行計算出圓面積再乘以高度。

顯而易見的 , 這樣會增加 Cylinder 與 Circle 的耦合程度 , 那麼未來如果要修改 Circle 的設計 , 例如將 r 改名為 radius, 或是計算圓面積時改乘 3.141

59, 都會很辛苦 , 因為必須一一檢查並修改所有與 Ci

rcle 相關的類別。

Page 122: SCJP ch11

122

鬆散耦合與高內聚力

所以鬆散耦合就是類別之間只保持最少的關連 , 以上例來說 , 應改為:

Page 123: SCJP ch11

123

鬆散耦合與高內聚力

內聚力 (Cohesion) 則是指類別功能的單一明確程度。 在設計類別時 , 除了應講究類別之間的鬆散耦合之外 ,

類別本身也應力求高內聚力 (High cohesion) 。 也就是說 , 類別的功能越單一明確越好 , 因為無論是要維護或重複使用都將越容易。

Page 124: SCJP ch11

124

鬆散耦合與高內聚力

底下來看一個低內聚力的例子:

這個類別幾乎可以獨立運作而不需要其他的類別 , 但其程式碼卻不易維護 , 也很難再利用。

Page 125: SCJP ch11

125

鬆散耦合與高內聚力

此時最好能將之切割為較小且功能單一明確的類別 ,

例如:

Page 126: SCJP ch11

126

11-4 Object 類別與包裝類別

如果您回想一下 , 在之前的章節中 , 曾經提過 "+"

這個運算子會在運算元中有 String 資料時進行字串連接。

此時 , 如果另外一個運算元並不是 String 物件 , 就會呼叫該物件的 toString () 方法 , 以便取得代表該物件的字串。

這聽起來似乎很完美 , 但是如果該物件沒有定義 to

String() 方法時會有甚麼結果呢?要瞭解這一點 , 請先看看以下的範例。

Page 127: SCJP ch11

127

Object 類別與包裝類別

Page 128: SCJP ch11

128

Object 類別與包裝類別

在第 1 ~ 2 行的 Child 類別並沒有定義任何方法 ,

而且也沒有繼承自任何類別 , 因此便不會有 toString

() 方法 , 那麼第 7 行使用 "+" 運算子進行字串連結時 , 會出錯嗎?

答案是不會 , 這個程式不但可以正常編譯 , 而且也可以執行 , 結果如下:

Page 129: SCJP ch11

129

Object 類別與包裝類別

我們暫且不管這顯示結果的意義 , 單單從這個程式可以正常執行 , 就可以知道 Child 類別其實是有 toSt

ring() 方法的 , 那麼這個 toString() 方法從何而來呢?

Page 130: SCJP ch11

130

11-4-1 類別的始祖 -- Object 類別

答案很簡單 , 在 Java 中定義有一個 Object 類別 ,

這個類別是所有類別的父類別。 亦即對於任何一個沒有標示父類別的類別來說 , 就相當於是繼承自 Object, 而 Object 類別定義有 toS

tring() 方法。 所以在剛剛的範例中 , 字串連接時執行的就是 Child

繼承自 Object 的 toString() 方法。

Page 131: SCJP ch11

131

類別的始祖 -- Object 類別 所以剛剛範例中的類別定義 , 就完全等同於以下的程式:

不管您有沒有明確的標示繼承自 Object, 您所定義的類別都會是 Object 的子類別。

Objec t 類別定義有幾個常用的方法 , 是您在自行定義類別時有必要依據類別本身的特性重新定義的 , 以下就分別討論之。

Page 132: SCJP ch11

132

public String toString()

誠如您在之前範例所看到 ,Object 類別中的 toString

() 方法傳回的字串如下:

其中 "Child" 是類別的名稱 (範例中是 Child 類別 ), 接著是一個 "@ ", 然後跟著一串奇怪的 16

進位數字 , 這串數字是物件的識別號碼 , 代表了範例中參照 c 所指的物件。

Page 133: SCJP ch11

133

public String toString()

在執行時期 , 每一個物件都對應到一個獨一無二的數字 , 稱為是該物件的雜湊碼 (Hash Code), 此數字事實上是由 Object 的另一個方法 hashCode() 來取得 (參見下一頁 )) 。

很顯然的 , 這個實作對於多數的物件來說並不適合 ,

因此 , 建議您為自己的類別重新定義 toString() 方法 , 以顯示適當的資訊。

Page 134: SCJP ch11

134

public int hashCode()

Object 的 hashCode() 方法會傳回前面所提到的雜湊碼 , 但傳回型別為 int 。例如修改前面的程式:

▪ 有關 hashCode() 的應用 , 我們留到第 18 章再介紹。

Page 135: SCJP ch11

135

public boolean equals(Object obj)

在之前的章節中 , 其實已經使用過 equals() 方法 ,

這個方法主要的目的是要和傳入的參照所指的物件相比較 , 看看兩個物件是否相等。

一般來說 , 您所定義的類別必須重新定義這個方法 ,

進行物件內容的比較 , 並依據比較的結果傳回 true

或是 false, 表示兩個物件內容是否相同 由於 Object 類別無法得知子類別的定義 , 因此 O

bject 類別所實作的 equals() 方法只是單純的比較參照所指的是否為同一個物件 ( 相當於用 == 比較 2 個物件 ) , 而不是比較物件的內容 , 例如。

Page 136: SCJP ch11

136

public boolean equals(Object obj)

Page 137: SCJP ch11

137

public boolean equals(Object obj)

由於在 Java 提供的一些有關為物件排序或是搜尋物件的類別中 , 都會依靠類別所實作的 equals() 方法來檢查兩個物件是否相等 , 因此在必要時請為您的類別重新定義 equals() 方法。

例如本章最前面介紹的 Circle 類別 , 就可以定義以下的 equals() 方法。

Page 138: SCJP ch11

138

public boolean equals(Object obj)

Page 139: SCJP ch11

139

public boolean equals(Object obj)

Page 140: SCJP ch11

140

public boolean equals(Object obj)

在重新定義 Object 的 equals() 方法時 , 要注意必須宣告為 public boolean, 而且參數必須是 Object

型別而非 Circle 型別 , 也就是參數要和 Object 中的定義完全相同 , 否則就變成是多重定義 (Overload

ing) 了。 另外在方法中最好能先用 instanceof 來檢查傳入的參數是否為 Circle 類別 , 以及是否為 null ( 為 nul

l 時 instanceof 的結果也會是 false), 否則執行到第 11 行強制轉型 ((Circle)o) 時就會發生錯誤。

Page 141: SCJP ch11

141

public boolean equals(Object obj)

▪ 除了前述的方法外 , Object 還有 clone()- 複製物件並傳回、 finalize()- 當物件被資源回收時會自動呼叫此方法以進行善後工作、 getClass()- 傳回物件所屬類別、以及與執行緒 (見第 15 章 ) 有關的幾種方法。

Page 142: SCJP ch11

142

11-4-2 代表基礎型別的包裝類別

您可能已經想到了 , 在之前的範例中 , 即便字串連接的是基本型別的資料 , 像是 int 、 byte 、或是 dou

ble 等等 , 也都可以正確的轉成字串。 可是基本型別的資料並不是物件 , 根本不會有 toStri

ng() 方法可以呼叫 , 那麼轉換成字串的動作究竟是如何做到的呢?

Page 143: SCJP ch11

143

代表基礎型別的包裝類別

答案其實很簡單 , 在 Jav

a 中 , 針對每一種基本資料型別都提供有對應的包裝類別 (Wrapper Class) :

Page 144: SCJP ch11

144

代表基礎型別的包裝類別

當基本型別的資料遇到需要使用物件的場合時 , Java

編譯器會自動建立對應該型別類別的物件 , 以代表該項資料。

這個動作稱為封箱 (Boxing), 因為它就像是把一個基本型別資料裝到一個物件 (箱子 ) 中一樣。

因此 , 當 int 型別的資料遇到字串的連接運算時 , Ja

va 就會先為這個 int 資料建立一個 Integer 類別的物件 , 然後呼叫這個物件的 toString() 方法將整數值轉成字串。

Page 145: SCJP ch11

145

代表基礎型別的包裝類別

相同的道理 , 當 Integer 類別的物件遇到需要使用 int 的場合時 , Java 編譯器會自動從物件中取出資料值 , 這個動作稱為拆箱 (Unboxing) 。例如:

Page 146: SCJP ch11

146

代表基礎型別的包裝類別

Page 147: SCJP ch11

147

代表基礎型別的包裝類別 在第 12 行中 , 傳入了 2 個 int 型別的資料給 C

hild 類別的 addTwoInteger() 方法 , 由於這個方法需要的是 2 個 Integer 物件 , 因此 Java 會進行封箱的動作 , 產生 2 個 Integer 物件 , 再傳給 addTwoInteger() 方法。

事實上 , 第 12 行就等同於 13 行 , 只是 Java 編譯器會幫您填上產生物件的動作而已。

在第 3 行中 , 就是 Integer 物件遇到需要使用 int 的場合 , 這時 Java 編譯器會自動幫您填上呼叫 Integer 類別所定義的方法 , 以取得其所代表的整數值 , 然後才進行計算。

Page 148: SCJP ch11

148

為什麼要有基本型別

從剛剛的說明看來 , int 和 Integer 好像功用重複 ,

只需要保留 Integer 類別 , 讓所有的資料都是物件不就好了?

其實之所以會有基本型別 , 是因為物件的使用比較耗費資源 , 必須牽涉到配置與管理記憶體。

因此 , 對於資料大小固定、又很常使用到的資料型別 ,

像是 int 、 double 、 byte 等等 , 就以最簡單的基本型別來處理 , 只在必要的時候才產生對應的物件 ,

以提高程式的效率。

Page 149: SCJP ch11

149

代表基礎型別的包裝類別

▪ 自動裝箱與拆箱的功能必須使用 JDK 5.0 ( 含 ) 以上的編譯器才有支援。有關於基本型別的包裝類別 , 在第 17 章還會有詳細的介紹。

Page 150: SCJP ch11

150

11-5 傳遞不定數量、或任意型別的參數

有了繼承之後 , 在程式的撰寫可以比較活 , 同時可以讓程式寫起來更為簡潔、易懂。

在這一節中 , 我們要將 11-2-4 節中的 Lands1.jav

a 加以改良 , 所要利用的就是 Java 的繼承機制。

Page 151: SCJP ch11

151

11-5-1 傳遞不定數量參數 - - 使用陣列

在 11-2-4 節中 , 撰寫了一個可以計算地價的程式 ,

其中 Calculator 類別的 calculatePrice() 方法可以依據傳入的 Land 物件 , 計算其地價。

可是如果有多塊地想要計算總價的話 , 就得一個一個用 calculatePrice() 計算 , 然後再加總。

針對這樣的問題 , 其實可以撰寫一個 calculateAllPri

ces() 方法 , 傳入一個 Land 物件的陣列 , 然後再從陣列中一一取出各別 Land 物件 , 計算地價並加總。

Page 152: SCJP ch11

152

傳遞不定數量參數 - - 使用陣列

先看看以下的程式 (由於其他類別與 Lands1.java

中一樣 , 這裡僅列出 Calculator 類別及 main() 方法 ):

Page 153: SCJP ch11

153

傳遞不定數量參數 - - 使用陣列

Page 154: SCJP ch11

154

傳遞不定數量參數 - - 使用陣列

Page 155: SCJP ch11

155

傳遞不定數量參數 - - 使用陣列

第 42 ~ 50 行就是新增的 calculateAllPrices() 方法 , 這個方法接受一個 Land 陣列 , 並計算 Land

陣列中所有 Land 物件所代表土地的地價總值。 這個技巧可以用來撰寫需要傳入未知個數參數的方法 ,

呼叫時只要將需要傳遞的參數通通放入陣列中即可。 如果這些參數的型別不一樣時 , 可以採用 Object 作為陣列元素的型別 , 由於所有的類別都是 Object 的子類別 , 所以任何物件都可以放入此陣列中 , 傳遞給方法了。

Page 156: SCJP ch11

156

傳遞不定數量參數 - - 使用陣列

接著在第 61 行中 , 使用了 7-4-2 節所介紹過的匿名陣列將 c 與 s 這 2 個 Land 物件放入陣列中 ,

傳遞給 calculateAllPrices() 方法。 這樣一來 , 不管想要計算地價的 Land 物件有幾個 ,

都可以使用同樣的方式呼叫 calculateAllPrices() 方法來計算了。

Page 157: SCJP ch11

157

11-5-2 傳遞不定數量參數 -- Varargs 機制

由於上一小節所使用的傳遞不定個數參數的技巧在許多場合都可以發揮用處 , 因此從 JDK 5.0 之後 , 提供了一種簡便的方式 , 稱為 Varargs (即 Variable A

rguments, 可變參數的意思 ), 可以將類似的程式簡化。

請看以下的範例 ( 一樣只顯示 Calculator 類別及 main() 方法 ) 。

Page 158: SCJP ch11

158

傳遞不定數量參數 -- Varargs 機制

Page 159: SCJP ch11

159

傳遞不定數量參數 -- Varargs 機制

Page 160: SCJP ch11

160

傳遞不定數量參數 -- Varargs 機制 這個程式和剛剛的範例作用一模一樣 , 事實上 , 經過

Java 編譯器編譯後 , 兩個程式的內容根本就相同。 在第 42 行中 , calculateAllPrices() 方法的參數型別變成 Land..., 類別名稱後面的 ... 就表示這個參數其實是一個陣列 , 其中每個元素都是 Land 物件。

當 Java 編譯器看到 ... 後 , 就會將這個參數的型別自動改成 Land[ ] 。

相同的道理 , 當 Java 編譯器看到第 61 行時 , 發現呼叫的方法所需的參數是 Land... 時 , 就會自動將後續的這一串參數放進一個匿名陣列中傳遞過去。

Page 161: SCJP ch11

161

傳遞不定數量參數 -- Varargs 機制

因此 , 這就和上一小節所撰寫的程式一模一樣了 , 只是撰寫起來更簡單。

不定參數的『 . . .』語法 , 只能用在方法中最後一個參數 , Java 編譯器會將呼叫時對應到此一參數及之後的所有參數通通放到一個匿名陣列中。

舉例來說 , 如果希望在計價時能夠直接指定單位價格 ,

就可以改成這樣。

Page 162: SCJP ch11

162

傳遞不定數量參數 -- Varargs 機制

Page 163: SCJP ch11

163

傳遞不定數量參數 -- Varargs 機制

Page 164: SCJP ch11

164

傳遞不定數量參數 -- Varargs 機制

1. 在 Calculator 類別中 , 只定義了一個 calPrices()

方法 , 可依照傳入的單價及任意數目的土地來計算出總價。另外由於加上了 static, 因此可直接用類別名稱呼叫 , 而不必先建立物件。

2. 第 48 行的呼叫動作中 , 4000 就對應到 price 參數 , 而 c 與 s 就對應用 Lands... 參數 , Java

編譯器會以匿名陣列來傳遞這 2 個參數。 如果把第 32 行寫成這樣 , 那編譯時就會發生錯誤:

Page 165: SCJP ch11

165

11-5-3 傳遞任意型別的參數 由於 Object 類別是所有類別的父類別 , 因此當方法中的參數是 Object 型別 , 表示該參數可使用任意型別的物件。

舉例來說 , 如果希望剛剛的 calPrices() 方法可以在傳入參數時 , 在每個代表土地的 Land 物件之後選擇性的加上一個整數型別參數 , 表示同樣大小的土地數量 , 比如說 , 傳入:

表示要計算地價的土地中 , 像 s 這樣大小的土地有 2 塊 , 而 c 這樣大小的土地只有一塊。

Page 166: SCJP ch11

166

傳遞任意型別的參數

那麼就可以新增一個多重定義的方法 , 讓這個方法接受任意個數的 Object 物件:

Page 167: SCJP ch11

167

傳遞任意型別的參數

Page 168: SCJP ch11

168

傳遞任意型別的參數

Page 169: SCJP ch11

169

檢查物件的類別 -- instanceof 運算子

第 42 行就是新增的方法 , 您可以注意到它所需的參數是一個 Object 陣列 , 而由於 Object 是所有類別的父類別 , 所以不管是整數還是 Land 物件 ,

都可以傳進來。 從 46 行開始 , 就一一檢視個別元素。不過如前面所說 , Object 類別的參照可以指向任何物件 , 為了防範使用者傳入錯誤的物件 , 必須檢查元素的類別。

Page 170: SCJP ch11

170

檢查物件的類別 -- instanceof 運算子

這裡就使用了 instanceof 運算子檢查參照所指的物件類別 , 像是第 48 行就檢查目前的元素是否為 L

and 物件 , 如果是 , 才進行地價計算的動作 , 否則再檢查是否為 Integer 物件 , 如果是 , 則當成前一地價的倍數來計算。

Page 171: SCJP ch11

171

強制轉型

在第 49 行會以強制轉型的方式 , 將目前的元素轉型為 Land 物件 , 然後呼叫其 area() 方法以計算地價 , 並暫存於 tmp, 再加總到 total 中。

在第 64 行則會將元素強制轉型為 Integer, 並透過 Java 編譯器自動拆箱的功能取出整數值;然後以前一塊土地的地價乘以此數值減 1 (因為之前已累加過一次 ), 再累加到 total 中。

Page 172: SCJP ch11

172

強制轉型

當 main() 方法中呼叫 calPrices() 時 , 傳入了 30

00,s,2,c 作為參數 , Java 編譯器發現沒有任何一個版本的方法符合這樣的參數 , 但因為 Object 是所有類別的父類別 , 所以 Java 編譯器就選擇了剛剛所撰寫的版本 , 建立了一個匿名的 Object 陣列 , 將參數一一放入此陣列中 , 傳遞給方法。

利用本章介紹的繼承 , 程式的撰寫就更富有彈性 , 只要能夠多多活用 , 就可以發揮繼承的優點。

Page 173: SCJP ch11

173

11-A 多型的應用:將各種形狀依面積排序

Page 174: SCJP ch11

174

1. Given:

Page 175: SCJP ch11

175

Which one is true ?A. Print 2.B. Print 4.C. Print 8.D. Compilation fails.E. An exception is thrown at runtime.

Page 176: SCJP ch11

176

2. Given :

Which two changes should be made to correct the compilation errors.A. Insert a super(); into the Child constructor.B. Insert a super(n); into the Child constructor.C. Insert a this(); into the Child constructor.D. Remove the public from all constructors.E. Change private at line 2 to protected.F. Change the name at line 2 to name = ""G. Change the name at line 7 to super.name.

Page 177: SCJP ch11

177

Page 178: SCJP ch11

178

3. Given:

What is the result?

A. AB B. ABC C. BC

D. BA E. C F. CBA

Page 179: SCJP ch11

179

4. Given:

What is the result?A. ... @!# B. ~~~ @!# C. ~~~ ~~~D. Compilation fails. E. An exception is thrown at

runtime.F. ... ~~~

Page 180: SCJP ch11

180

Page 181: SCJP ch11

181

5. Which three statements concerning is-a and has-a relationships are true? (Choose three)

A. Inheritance is is-a relationship.

B. Inheritance is has-a relationship.

C. Member variable can be used to implement the is-a relationship.

D. Member variable can be used to implement the has-a relationship.

E. Child class is-a parent class.

F. Parent class is-a child class.

G. Parent class has-a child class.

Page 182: SCJP ch11

182

6. Place the Classes in Type column and Relationships in Relationship column, to match the is-a or has-a relationships.

Page 183: SCJP ch11

183

Page 184: SCJP ch11

184

Page 185: SCJP ch11

185

7. Given:

Page 186: SCJP ch11

186

Which code inserted at line 8 will compile? (Choose all that apply.)

A. private void print(String s) { }B. protected void print(String s) { }C. String print(String x) { }D. String print(int i) { return "ok"; }E. void print(String s, String t) { }F. B print(String z) { }G. B print(A a) { return (B)a; }H. Object print(A a) { return a; }

Page 187: SCJP ch11

187

8. Given the following code, place the correct Output Strings t

o the Output Sequence:

Page 188: SCJP ch11

188

Page 189: SCJP ch11

189

Page 190: SCJP ch11

190

9. Given:

What is the result?A. ABC B. ABAC C. BACAD. BC E. CB F. CABA

Page 191: SCJP ch11

191

11. Given:

Page 192: SCJP ch11

192

Page 193: SCJP ch11

193

10. Drag and drop the appropriate sentence into the following empty boxes:

Page 194: SCJP ch11

194

Which code inserted at line 7 will compile?(Choose all that apply.)

A. This() { this.i = 5; } B. This() { super.i = 5; }

C. This() { super(5); } D. This() { Super(5); }

E. This() { this(5); } F. This() { This(5); }