• 沒有找到結果。

第七章第七章 物件導向設計: 物件導向設計: 類別與物件 類別與物件

N/A
N/A
Protected

Academic year: 2022

Share "第七章第七章 物件導向設計: 物件導向設計: 類別與物件 類別與物件"

Copied!
84
0
0

加載中.... (立即查看全文)

全文

(1)

第七章 第七章 物件導向設計: 物件導向設計:

類別與物件 類別與物件

課前指引

在本章中,我們將正式進入物件導向程式設計的領域,雖然我們在前面章節

,已經使用過某些Java類別庫的類別或物件(例如:Math類別、String物件

),但卻未曾學習如何建立一個物件(事實上建立物件必須先宣告類別)。

在本章中,我們將從頭教您如何使用Java並以物件觀點設計程式,逐漸體驗 物件導向程式設計帶來的好處,尤其是在發展中大型專案時,物件導向程式 設計更是目前最佳的解決之道。

章節大綱

7.1 物件導向程式設計 7.2 物件導向的三大特性

7.7 物件引數的傳參考值呼叫

7.3 Java的類別

7.8 this關鍵字 7.9 物件陣列 7.4 Java的物件

7.5 方法

7.10 類別變數與類別方法 7.11 本章回顧

7.6 建構子

(2)

3

Java是一個物件導向程式語言(OOPL;

Object-Oriented Program Language)

所謂物件導向設計理念,就是利用軟體模擬現實 生活中實體所擁有的特性與行為,這些實體就是 物件,而每一個物件都可以擁有各自的屬性及方 法

物件導向程式設計(OOP ;Object-Oriented Programming)是一種以物件觀念來設計程式的程 式設計技巧,它透過物件的方法產生互動以完成 程式要求。

7.1 物件導向程式設計

在純物件導向的程式中,每一個運作實 體都可視為某種類別衍生出來的物件

而類別則較具抽象的概念,也就是某些具有共同 特性物件的集合

換句話說,物件就是類別的實體。

物件導向具有封裝、繼承、多型等特性,原則上 每個物件相互獨立且無關聯性

而物件導向程式設計就是依照物件的方法產生互 動以完成要求。

7.1 物件導向程式設計

(3)

5

傳統的結構化程式設計將問題切割成許 多小問題(模組),由於這些模組的存 在目的是為了解決大問題而設計的,因 此,這些模組可能會存取同一個資料結 構,這將導致模組之間的獨立性質降低

舉例來說,某兩個模組可能都會修改陣列資料結 構中的元素,因此當我們希望將某一個模組的操 控對象從「陣列」改為其他的資料結構時,另外 一個模組也必須跟著改變。

因此,結構化程式設計雖然對於某些問題可以快 速尋得解決方案,但對於日後的維護則顯得不足

7.1 物件導向程式設計

故後來又發展了以物件為基礎的程式語言。

並且又可以分為物件基礎程式語言與物件導 向程式語言兩類。

這兩類程式語言都是以物件為出發點,藉由物件與物 件之間的互動完成問題的解答,比較符合真實世界環 境。

物件基礎程式語言最著名者為JavaScript

它允許設計者使用物件來設計程式,但無法提供繼承等機制 來擴充程式,不利於大型程式的開發,因此常用於客戶端網 頁等程式設計。

Java是物件導向程式語言

提供完整的繼承、封裝、多型等特性,適合開發大型程式。

7.1 物件導向程式設計

(4)

7

物件之間的互動是透過訊息(Message)的傳 遞來達成

發送訊息的物件稱之為發送者(sender) 接受訊息的物件稱之為接收者(receiver)

當物件需要其他物件服務時,會發送訊息給接收者,

接收者在收到訊息後,會執行符合要求的方法完成任 務以提供服務。

由於每一個物件都是一個獨立的個體,因此改良某一 物件的內容時,其他不同類別的物件並不需要跟隨著 變動。

如此一來,在尋求大型或超大型方案的解答時,不但比結構 化程式設計容易分析問題,更有助於日後的維護與修改。

7.1 物件導向程式設計

真實世界中的所有具體或抽象的事物,都可 以將之視為一個『物件』。

例如:您可以把一架飛機想像成是一個物件(Object)

;而飛機的零件(例如:座椅、引擎、操縱桿)則是 較小的物件

明顯地,這些物件仍舊可以再細分為更小的物件(例如:螺 絲釘)。

在程式設計中,Java的物件實際上是一些程 式碼和資料的組合,物件必須能夠單獨成為 一個完整單元,也可以組合成更大的物件。

例如:一個按鈕或一個表單都是一個物件。而一個應 用程式最大的物件就是應用程式的本身。

7.1.1物件(object)

(5)

9

屬性 (Property)

物件擁有許多特性(attribute),這些特性代表了一個物 件的外觀或某些性質

例如:民航機物件的最高速度就代表了該飛機的一種特 性、民航機物件的重量、載重量…等等也都可以用來代 表飛機的某些特性

這些特性在物件導向程式設計中稱之為屬性(Property) 事實上,有的時候在取得物件的某些屬性時,所得到的 也可能是另一個(子)物件(若該屬性的資料型態是一 個類別)。

例如:民航機的引擎也是一個物件,因為它可以由更多更小的零件來 組成,同時引擎物件也存在自己的方法(Method),例如:引擎點火。

在程式設計或執行階段,我們可以藉由改變屬性值來改變 整個物件的某些特性,完成我們想要的物件表示形式。

例如:將民航機物件的機殼漆成藍色,將按鈕背景顏色 設為黃色

7.1.1物件(object)

在Java語言中,屬性(Property)被稱為Field(有 人翻譯為欄位;本書有時也翻譯為成員資料變數

),並且被區分為不同的等級

某些等級的成員資料變數並不允許外部程式加以修改,

而必須透過該物件的方法,才能夠修改這些成員資料變 數。

因此具備保護資料的功能。

7.1.1物件(object)

(6)

11

方法 (Method)

每個物件都擁有不同數量的行為(method),這些 行為在物件導向程式設計中稱之為方法(Method)

。不同種類的物件所擁有的方法大多不同,但同 類物件擁有的方法則大致相同。

所謂方法,也就是為了完成該物件某些目標的處 理方式。

例如:飛機類別的物件有許多方法使得飛機變得有些用 途,這些方法如起飛、降落、逃生等等。

每個方法都有許多的細節

例如起飛,可能包含『發動引擎、、、、直到拉動操縱 桿』,這些就是起飛方法的細節。

7.1.1物件(object)

在Java中,方法也稱之為method(有些書籍翻譯為方 法;本書翻譯為方法或函式,也就是成員函式)。

同時Java的method也和Field一樣,被區分為多種等級

某些等級的成員函式同樣不允許外部程式呼叫執行(只能做 為內部成員函式呼叫執行的對象)

因此達到封

對於某些由他人完成的物件而言,該物件同樣必須提 供許多關於物件的方法,如此一來,我們就不需要了 解這些方法的細節,而能夠快速運用物件來完成工作

例如飛機物件提供了起飛方法,我們只要指定執行起飛方法

,而不需要了解起飛過程的種種細節,飛機就會起飛。

裝物件的功能。

7.1.1物件(object)

(7)

13

7.1.1物件(object)

圖7-1 類別與物件關係圖

類別

物件雖然是獨立的個體,不同的物件可以擁有相 同的屬性及方法,例如:「一架民航機」和「一 架戰鬥機」各是一個物件,但兩者的速度、爬升 力、重量、載重量雖不相同,但卻同樣擁有這些 屬性以便表達完整物件的各個特性。所以,不同 的物件若擁有共同的屬性,但因為屬性內容的不 同,因此可以創造出同類性質但卻獨立的不同物 件。而同類型的物件,則構成了類別(Class)。

例如每一個按鈕都是一個物件,屬於按鈕類別之下所建 造出來的物件實體,但是由於按鈕的名稱不同,因此視 為不同且獨立的物件。

7.1.2類別(class)

(8)

15

更明確地說,類別才是物件導向程式設計最基本 的單元

以上例來說,「民航機」和「戰鬥機」都是物件,而『

飛行器』或『飛機』才是類別。不同的只是在建立該類 別的實體物件時,給予不同的屬性而已。

事實上,在實際的物件導向程式設計中(例如 Java),我們必須先定義類別,然後才能夠透過 類別宣告各個屬於該類別下的物件,接著再設定 物件的屬性來代表該物件某方面的特性,並使用 物件的方法來操作物件。

7.1.2類別(class)

從由下而上的角度來看,類別是許多同類物件的集 合

所謂同類物件,代表擁有相同屬性(資料變數)及方法(函式

)的物件

例如:每一台『飛行器』類別下的物件,都包含「速度」、「爬升力

」、「重量」、「載重量」等變數,也包含「起飛」、「降落」、「

逃生」等方法,以便完成工作。由於我們可以將這些變數設定為不同 的屬性值,因此可以在該類別下造就各式各樣的物件

如同「民航機」和「戰鬥機」各是一個物件,而『飛行器』則是類別

在純物件導向的程式中

程式碼是以類別為基礎 物件只是類別的實體

就如同汽車是一個類別,它是一個抽象的名詞

而車牌號碼為010、015等的車輛是汽車類別的兩個物件

實際運作時「通常」依靠的是物件。我們將於下一小節中,更 深入介紹物件導向程式語言的特性。

7.1.2類別(class)

(9)

17

7.1.2類別(class)

老師的叮嚀

有些人在說明類別與物件的關係時,會將類別視為一種板模,而物件則 是依靠板模建立出來的實體,就如同雞蛋糕板模是類別,而每一個實際 做出來可以吃的雞蛋糕是物件。

這種比喻方式在某種程度上說得通,不過仍舊不能完全說明Java類別與 物件的實際應用,因為Java也允許在未產生物件實體時,存取變數或執 行方法,也就是靜態變數與靜態方法。

老師的叮嚀

有些人在說明類別與物件的關係時,會將類別視為一種板模,而物件則 是依靠板模建立出來的實體,就如同雞蛋糕板模是類別,而每一個實際 做出來可以吃的雞蛋糕是物件。

這種比喻方式在某種程度上說得通,不過仍舊不能完全說明Java類別與 物件的實際應用,因為Java也允許在未產生物件實體時,存取變數或執 行方法,也就是靜態變數與靜態方法。

圖7-2 類別生成物件

物件導向程式設計具有某些特點如下,使得它 更適合用以開發、管理大型程式。

封裝性、繼承性、多型性

封裝性(encapsulation):

封裝性可以將物件區分為可被外界使用的特性及受保護的 內部特性;

換句話說,除非是允許外部程式存取的資料,否則外部程 式無法改變物件內的資料。如此就能夠達到封裝保護資料 的特性。

更細分來說,物件導向程式設計將物件的資料與方法至少 區分為三種等級:(Java除了下列等級之外,尚有

Package等級)

public :公用等級。

private :私用等級。

protected :保護等級。

7.2物件導向的三大特性

(10)

19

其中私用(private)等級的資料與方法,只允許相同類 別內的程式碼取用

而保護(protected)等級的資料與方法,只允許相同類 別及衍生類別(何謂衍生類別後述)的程式碼取用 至於公用(public)等級的資料與方法,則開放給任何程 式碼取用。

7.2物件導向的三大特性

圖7-3 封裝性

以圖7-3為例,一個講究衛生的食品餅乾工廠,其內部機房就 具有封裝特性

對外,它只開放原料與成品兩個可存取的屬性,並且提供製作 方形餅乾、製作星形餅乾、製作圓形餅乾等操作方法的按鈕。

原料屬性對外是可存取的,例如您可能放入的是三號麵粉,但也可以 改為二號麵粉。

成品也是可以存取的,例如您可以把餅乾吃掉、裝箱或捏碎報廢。

被封裝的屬性則是不可存取的,除非透過外部方法間接存取

例如製作過程中,可能需要水,而我們無法直接變動水的種類,必須 透過對外的外部方法(也就是純水或礦泉水按鈕)來完成

換句話說,在這個範例中,我們無法隨意將水變更為海洋深層水,因 為我們無法直接進入到機房內部。

7.2物件導向的三大特性

機房物件

公開屬性:原料、成品 私有屬性:水……

公開方法:使用純水、使用礦泉水、方形餅乾製作、星形餅乾製作、圓形餅乾製作 私有方法:機器A啟動、機器B啟動、機器A快轉……

(11)

21

同理,由於我們無法進入機房內部直接操作機器

,所以只能透過對外操作面板上的三個按鈕選擇 要執行的製作程序。

當我們按下某一個按鈕時,相當於呼叫機房物件 的一個公開方法

此方法的細節則可以動用到機房內的所有資源

可是機房的設計一但完成後,這些方法的細節也就固定 了

對於使用機房物件的使用者而言

他只能選擇執行某一個按鈕方法,而無法更動按鈕方法 的執行內容細節。

7.2物件導向的三大特性

更進一步來說,假設我們是設計機房物件的程式設 計師

這代表著我們可以進入機房,也可以設計各種餅乾的製作細節 但即使進入了機房,當我們面對每一台機器時,由於每一台機 器也都是一個物件(同樣也具有封裝性),故我們只能操作機 器對外的按鈕,而無法進入機器的內部。

因此,封裝性使得問題變為分層負責的狀態,更符 合生活上的作事原則。

設計物件者有責任將物件設計的完整且不會出錯,也需要留下 一些對外可操作的方法,必要時也可留下一些外部可存取的屬 性。

而使用物件者,則只能夠透過物件對外的屬性與方法來利用物 件達到自己的需求。

7.2物件導向的三大特性

小試身手7‐1

「電子鬧鐘」物件具有封裝性,試以電子鬧鐘為例,說明電子鬧鐘的哪一些屬性為 公開屬性?哪一些為私有屬性?哪一些為公開方法?哪一些為私有方法?

小試身手7‐1

「電子鬧鐘」物件具有封裝性,試以電子鬧鐘為例,說明電子鬧鐘的哪一些屬性為

公開屬性?哪一些為私有屬性?哪一些為公開方法?哪一些為私有方法?

(12)

23

繼承性(inheritance):

繼承性是為了達成重覆使用目的所採取的一種策略

例如:一個滑鼠類別只要加上滾輪裝置,就變成了滾輪滑鼠 但滾輪滑鼠也同樣可以上下左右移動改變指標位置,也可以 按兩下執行程式,只不過現在又多了一個滾輪使得瀏覽網頁 時更加方便

因此,這個滾輪滑鼠類別可繼承滑鼠類別再加以擴充。

在物件導向程式設計中,允許使用者定義基底類別 (base class)與衍生類別(derived class)

其中衍生類別允許繼承基底類別的屬性及方法,並「加入新 的屬性及方法」或者改寫(override)某些繼承的方法,改成 適用於本身的方法。

有了這項特性,在開發大型程式時,我們就可以延續已經完 成的技術,再加以擴充。

7.2物件導向的三大特性

7.2物件導向的三大特性

(13)

25

多型性(Polymorphism):

就多型性而言,其實它是一個非常抽象的名詞,代表著一 種彈性。

舉例來說,所謂開水人人會煮,各有巧妙不同,基本上,

瓦斯爐、電磁爐、熱水瓶都可以煮開水,所以『煮開水』

其實是一個相當抽象的名詞

使用瓦斯爐、電磁爐、熱水瓶的煮開水方式都不一樣,但

『煮開水』一詞卻涵蓋了這些差異性的行為,而此種特質 則稱為「多型性」。

多型的實作方法有很多種

其中一種是改寫(override;或稱覆寫),它與繼承有關

例如瓦斯爐衍生類別、電磁爐衍生類別、熱水瓶衍生類別都繼承 自加熱器基底類別,該基底類別中定義了加熱的方法,我們則必 須在衍生類別內,改寫這個方法,使得加熱的細節有所不同 如此一來,由不同衍生類別所產生的物件,執行方法時的細節就 會不同了。

7.2物件導向的三大特性

7.2物件導向的三大特性

圖7-5 多型性

(14)

27

另一種屬於多型的技術稱之為多載(overload)

它允許程式中出現相同名稱但不同署名的函式,我們在上一章已 經介紹過了。

由以上兩種多型的機制可知,多型是一種呼叫 相同名稱函式,但卻可執行不同函式內容的機 制,因此,又有人將『多型』稱之為『同名異 式』。

編譯器在實作多型的功能時,依靠的是動態繫結來達成。

傳統上(在非物件導向語言中),我們在呼叫函式時,必 定會註明要呼叫哪一個函式,而編譯器在編譯階段就可以 知道各個函式在記憶體中的位址(當然這是個相對位址)

,因此只要我們註明了要呼叫哪一個函式,編譯器在編譯 階段就可以將該位址記錄在機器碼中,所以執行到該機器 碼指令時,就會去呼叫那一個函式,不可能執行別的函式

7.2物件導向的三大特性

而動態繫結則是

程式碼中可能有好幾個同名的函式,我們雖然指明了要 執行哪一個名稱的函式,但並未完全指定要執行同名中 的哪一個函式。

因此,編譯器在處理這種函式呼叫時,會編譯為由傳入 引數來決定要執行哪一個函式,而這個引數將會是函式 的起始位址,所以只有等到程式真的執行到該機器碼並 傳入引數時,才會知道要執行的是哪一個函式。

7.2物件導向的三大特性

(15)

29

上述物件導向設計的三大特性,Java當然全部都支援

在本章中,您將學習到Java如何完成物件導向中軟體元件 的封裝性。

並且在本篇的後面章節將介紹Java的其他程式設計技巧

這些設計技巧將足以完成物件導向設計的繼承性及多型性。

經過以上的介紹,相信您對於物件導向設計有了初步的概念

,在進入下面的內容之前,我們再說明一次關於Java的物件 與類別。

物件是由類別所衍生,例如透過String類別,我們可以宣 告一個String物件。

而物件名稱是一個參考。透過new我們可以產生物件的實 體,並將某一個物件名稱指向它,以便操作該物件。

因此,類別有fields與methods,而該類別衍生的物件也有相同的 fields與methods。

7.2物件導向的三大特性

還記得我們在介紹陣列時,曾說明陣列是將 相同資料型態的變數集合起來形成一個結構 嗎?

而類別則可以將不同資料型態的變數集合起來形成一 個結構

這些變數將成為類別的成員

不僅如此,我們還可以為類別定義屬於該結構的函式

類別是物件導向程式最基本的單元,是產生 同一類物件的基礎

類別可以由使用者自行定義,以下是在Java中定義類 別的語法:

7.3 Java的類別

(16)

31

定義類別語法:

[封裝等級] [修飾字] class 類別名稱 [extends 父類別] [implements介面]

{

[封裝等級] [修飾字] 資料型態 field名稱1;

[封裝等級] [修飾字] 資料型態 field名稱2;

: :

[封裝等級] [修飾字] 回傳值資料型態 method名稱1(參數串宣告) {

method的內容 }

[封裝等級] [修飾字] 回傳值資料型態 method名稱2(參數串宣告) {

method的內容 }

: : : }

7.3 Java的類別

宣告fields

定義method

定義method

語法說明:

(1)類別定義的第一行,我們可以視為類別的宣告

。當中所有的[]都可以省略,因此我們暫不討論

(後面會陸續介紹)

而其中類別的[封裝等級],只有public或不出現兩種,

當「出現public」時,代表該類別為「主類別」,而主 類別必須與檔名取相同的名稱。

同時,只有被宣告為public的類別才能被import進來。

在本章中,我們宣告類別時,都不會出現該欄位(除主 類別外)。

(2)在類別中定義的資料與函式,分別稱為fields 與methods。關於fileds及methods的翻譯與物件 導向設計的對照請見下表。

7.3 Java的類別

(17)

33

(3)類別中可以對fileds及methods定義多種『封 裝等級』如下,在本章中,我們將先說明『公用 等級』及『私用等級』,而『保護性等級』則留 待繼承一章中再做說明。如果省略宣告成員的封 裝等級,則該成員只能被同一個Package的類別存 取,而無法被不同Package的類別存取,此稱為「

package封裝等級」(詳見第11章)。

Java原始

說明文件 物件導向設計 C++ 本書翻譯 其他 field 屬性(Property) 成員變數 資料欄位、成員變數、

成員資料、屬性 有時不翻譯

method 方法(Method) 成員函式 函式、成員函式、方法 有時不翻譯

7.3 Java的類別

表7-1 filed與method翻譯對照表

public:公用等級資料型態 private:私用等級資料型態 protected:保護性等級資料型態 空白:package等級

(4)在類別中,『不同的存取等級』代表『不同的 資料保護方式』,資料之所以需要保護,主要是 為了讓各類別的資料獨立開來,不易被其他不相 干的函式修改,達到物件導向程式設計中,「資 料封裝」及「資料隱藏」的功能。

(5)根據上述定義類別的語法,我們可以利用下圖 來示意類別的內部結構:(當中省略了package等 級)

7.3 Java的類別

(18)

35

(6)私用等級資料只能被同一個類別的成員函式存 取;而公用等級的資料則除了類別內的成員可以 存取之外,其他外部的函式也都可以存取宣告為 公用等級的資料。

圖7-6 資料等級(省略package等級)

7.3 Java的類別

(7)宣告fields與定義methods的順序可以交錯。

(8)field可以設定初值且該初值會在物件實體產生時

,自動被設定。

(9)類別的命名在第二章曾經提過,本書所採用的習 慣為第一個字母大寫(通常命為C,代表class),其 後由一個以上的單字所組成,每個單字的開頭字母為 大寫,其餘為小寫

例如:CPerson,CStack,CCircularQueue。

【範例1】

定義一個CStudent類別,將其成績、地址、電話宣告 為『私用等級資料型態』;學號、姓名、科系宣告為

『公用等級資料型態』。

7.3 Java的類別

(19)

37

class CStudent {

private  float   score;

private  String  address;

private  int     phone;

public   int     id;

public   String  name;

public   String  major;

}

7.3 Java的類別

class CStudent {

private  float   score;

public   int     id;    

private  int     phone;

public   String  name;

private  String  address;  

public   String  major;

}

相同封裝等級的不 一定要擠在一起

【範例2】

延續範例1,在CStudent類別中,宣告兩個『公用等 級』的(成員)函式,分別為showId( )和

showMajor( ),函式用途分別為『顯示學號』及『顯 示科系』。

class CStudent {

private  float   score;

private  String  address;

private  int     phone;

public   int     id;

public   String  name;

public   String  major;

public int  showId( ){...}

public void showMajor( ){...}

}

7.3 Java的類別

顯示並回傳學號,回傳型態為int

顯示科系,回傳型態為void

(20)

39

範例說明:

上述範例,當物件被實際建立之後,id,name,major可 以被外部函式所取用,showId()與showMajor()也可以 被外部程式所執行,但score、address與phone只能被 同一類別的成員函式存取(例如showId成員函式)。

class CStudent {

private  float   score;

private  String  address;

private  int     phone;

public int  showId( ){...}

public void showMajor( ){...} 

public   int     id;

public   String  name;

public   String  major;

} 或

7.3 Java的類別

顯示並回傳學號,回傳型態為int

顯示科系,回傳型態為void

類別是許多資料變數及函式合成的抽象 資料型態,和陣列很相似。當我們想要 使用類別時,必須透過宣告變數並產生 實體後才能夠使用(除了僅使用被宣告 為static的變數與方法外)。

而由類別宣告的變數,稱之為物件參考或物件變 數。而產生的實體則稱為物件實體(instance)。

對照圖7-1,我們可以將Java的類別與物 件重新描述如下圖:

7.4 Java的物件

(21)

41

7.4 Java的物件

圖7-7 Java類別與物件關係圖(請與圖7-1對照比較)

宣告物件變數

宣告物件變數與宣告一般變數差不多,只不過是把原 始資料型態改為類別名稱,語法如下:

類別宣告物件變數語法

語法說明:

上述語法可以出現在任何類別內,也可以出現在任 何method內。當宣告物件變數後,由於物件變數是 一個參考,故開始時並未指向任何實體,此時的內 容會被初始化為null。

【範例】:宣告一個CStudent類別的物件變數,名稱 為John。

類別名稱 物件變數名稱;

7.4.1 宣告物件變數與產生物件實體

(22)

43

【範例】:宣告一個String類別的物件變數,物件名 稱為str1。

【說明】 String類別已經被定義於java.lang Package,故載 入該類別即可 。

class CStudent {

……類別定義……

}

CStudent John;   //宣告物件變數John,本敘述應出現在類別或方法內

import java.lang.String;

String str1;   //宣告String物件變數str1,本敘述應出現在類別或方法內

7.4.1 宣告物件變數與產生物件實體

產生物件實體

產生物件實體使用的是new運算子,語法如下:

【產生物件實體語法】

語法說明:

這個語法由於不屬於變數宣告,故只能出現在method內

。上述語法除了可以產生物件實體,並將實體的參考設 定給物件變數之外,還可以透過引數串列呼叫特定的建 構子。建構子(Constructor)是一個特殊的函式,可以 在物件實體產生時,進行一些初始化動作。如果不需要 傳遞引數,則可以省略引數串列。

【範例】:為CStudent類別的物件變數John產生 一個實體。

John = new CStudent();

物件變數 = new 類別名稱(引數1,引數2…);

7.4.1 宣告物件變數與產生物件實體

(23)

45

【範例】:為String物件變數str1產生一個實體 並設定其初始值為"Hello"。

str1 = new String("Hello");

【說明】

String類別不是我們所開發的類別,其建構子 (Constructor)已經由JDK完成

在Java說明文件中,可以查詢Constructor Summary文 件段,會發現許多與類別同名的函式,這些函式就是建 構子。

7.4.1 宣告物件變數與產生物件實體

宣告物件變數並產生實體

我們可以於宣告物件變數時,同時產生物件實體,語 法如下:

語法說明:

上述語法只是將之前的兩個語法合併。上述語法可以出現在 任何類別內,也可以出現在任何method內。

【範例】:宣告CStudent類別的物件變數John,並產 生一個實體,使John指向該實體。

CStudent John = new CStudent();

【範例】:宣告String物件變數str1,並產生一個實 體,實體內容初始化為"Hello",並使str1指向該實 體。

String str1 = new String("Hello");

類別名稱 物件變數名稱 = new 類別名稱(引數1,引數2…);

7.4.1 宣告物件變數與產生物件實體

(24)

47

當物件實體被產生以後,我們就可以合 法存取公用等級的fields以及執行公用 等級methods(如果敘述處於同一類別則 不限封裝等級皆可存取)

而且存取方法很簡單,只要在物件與欲取用資料 之間加上『.』運算子即可,如下語法:

【存取成員資料與執行成員函式語法】

物件.成員資料

物件.成員函式(引數1,引數2,…)

7.4.2 存取成員變數與執行成員函式

語法說明:

(1)私用資料與函式,只能被同一類別內的成員函式存 取與呼叫。(至於保護資料與函式則留待繼承一章再作 說明)

(2)上述語法是外部程式的存取與呼叫語法。如果是同 一類別內的存取與呼叫,則不需要指定物件,因為當它 被執行時,它已經是物件本身。

【範例1】將John物件的id變數設定為9923807,

設定John物件的name變數為"John"。

John.id = 9923807;

John.name="John";

【範例2】透過showMajor()成員函式顯示John的 科系名稱。

John.showMajor();

7.4.2 存取成員變數與執行成員函式

(25)

49

【錯誤範例】私用資料與函式,只能被類別內的成員 函式存取,所以下列是錯誤的敘述:

【觀念範例7-1】:定義類別、宣告物件並存取物件 的公用成員資料。

範例7-1:ch7_01.java(隨書光碟 myJava\ch07\ch7_01.java)

public class 主類別名稱 {

static CStudent John;

public static void main(String args[]) {      

John = new CStudent();   

John.score = 85.5;        //錯誤,因為score是私用資料

John.address="台北市大安區"; //錯誤,因為address是私用資料 }

}

7.4.2 存取成員變數與執行成員函式

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

/* 檔名:ch7_01.java 功能:定義汽車類別與宣告3個物件 */

package myJava.ch07;

import java.lang.*;

public class ch7_01 //主類別 {

public static void main(String args[]) {

CCar bus = new CCar();

CCar truck = new CCar();

CCar taxi = new CCar();

bus.name="公車";

bus.wheel=6;

bus.person=40;

truck.name="卡車";

truck.wheel=8;

truck.person=3;

taxi.name="計程車";

taxi.wheel=4;

taxi.person=5;

//taxi.engine="V16";

7.4.2 存取成員變數與執行成員函式

宣告CCar類別下的3個物件變數並產 生實體,物件名稱分別為

bus,truck,taxi。

可存取公用等級的變數

不可存取私用等級的變數

(26)

51

執行結果:

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

System.out.print(bus.name + "有" + bus.wheel + "個輪子");

System.out.println(",可載" + bus.person + "人");

System.out.print(truck.name + "有" + truck.wheel + "個輪子");

System.out.println(",可載" + truck.person + "人");

System.out.print(taxi.name + "有" + taxi.wheel + "個輪子");

System.out.println(",可載" + taxi.person + "人");

} } class CCar {

public int wheel;

public int person;

public String name;

private String engine;

}

公車有6個輪子,可載40人 卡車有8個輪子,可載3人 計程車有4個輪子,可載5人

7.4.2 存取成員變數與執行成員函式

定義CCar類別,包含3個公用等級變數 wheel、person、name,以及一個私用 等級變數engine。

範例說明:

(1)當成功編譯此範例後,您會發現目錄底下除了

ch7_01.class之外,還會出現CCar.class檔,這是因為 我們除了主類別外,另外又定義了一個類別CCar。

(2)

第14~16行,設定bus物件的公用等級變數。

第18~20行,設定truck物件的公用等級變數。

第22~24行,設定taxi物件的公用等級變數。

(3)第25行是不合法的敘述,因為engine是私用變數,

所以不允許外部程式(非同類別的函式敘述)存取該變 數

(但允許相同類別下的各級成員函式存取,詳見下一節)。

7.4.2 存取成員變數與執行成員函式

(27)

53

方法(method)又稱為成員函式,它與成員變 數一樣,除空白(未宣告封裝等級)的

package等級之外,封裝等級還可宣告為 public、private、protected等三種等級。

並且只有public等級的成員函式可以被外部程式

(非同類別的函式敘述)呼叫執行

而private等級的成員函式只能被其他同類別的成 員函式呼叫執行但不可以被外部程式呼叫執行 至於protected等級的成員函式則留待繼承一章再 作說明。

7.5 方法

到目前為止,我們了解到private等級的成員變數 或函式無法被外部程式所取用與執行,因此能夠 達到物件導向「封裝」的目的。而public等級的 成員變數或函式可以被外部程式所取用與執行,

是物件導向封裝時對外唯一的管道,同時private 等級的成員變數或函式也可以被public等級的成 員函式呼叫

因此外部程式如果想要取用private等級的成員變 數,可以先呼叫public等級的成員函式,再由 public等級的成員函式修改private等級的成員變 數,如此不但達到封裝的目的,也可以在類別中 增加了設計程式的彈性。

7.5 方法

(28)

55

7.5 方法

圖7-8 外部程式存取private等級的資料必須透過public等級的方法來完成

在上一章我們已經提過如何在主類別內定義函式,

由於Java的函式必定隸屬於某一個類別,故語法大 致相同

只不過,您必須把函式定義放在類別內部。以下是定義方 法的詳細語法:

【在類別內定義成員函式語法】

在類別內定義成員函式語法

語法說明:

關於[修飾字]部分,我們暫時省略,而封裝等級則為public、

private、protected或不填。

[封裝等級] [修飾字] class 類別名稱 [extends 父類別] [implements介面]

{

[封裝等級] [修飾字] 回傳值資料型態 method名稱 //定義method {

method的內容 }

}

7.5.1定義方法

(29)

57

【範例】請宣告並定義CMyClass類別中的相關成員函式如 下:

公用等級的成員函式funcA,不接受任何引數,也無回傳值。

公用等級的成員函式funcB,接受兩個整數引數,並回傳一個整數回 傳值。

私用等級的成員函式funcC,不接受任何引數,也無回傳值。

7.5.1定義方法

class CMyClass {

public void funA()       //公用成員函式 {

………成員函式定義………

}

public int funcB(int m,int n)  //公用成員函式 {

………成員函式定義………

}

private void funcC()      //私用成員函式 {

………成員函式定義………

} }

私用性(private)資料變數無法被外部程式存取,

但可以透過同一類別的成員函式(等級無限制)

來存取它,彈性應用私用的資料變數。

【觀念範例7-2】:透過公用成員函式存取私用資 料與成員函式。

範例7-2:ch7_02.java(隨書光碟 myJava\ch07\ch7_02.java)

7.5.2透過成員函式存取私用性資料變數

1 2 3 4 5

/* 檔名:ch7_02.java 功能:透過公用成員函式存取私用資料與成員函式 */

package myJava.ch07;

import java.lang.*;

(30)

59 6

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

public class ch7_02 //主類別 {

public static void main(String args[]) {

CMyClass X = new CMyClass();

CMyClass Y = new CMyClass();

X.initVar(); //在X物件上,執行initVar成員函式 Y.initVar(); //在Y物件上,執行initVar成員函式 X.addVar(10); //在X物件上,執行addVar成員函式 System.out.print("物件X\t");

X.showVar();

System.out.print("物件Y\t");

Y.addVar(5); //在Y物件上,執行addVar成員函式 Y.showVar();

System.out.print("物件Y\t");

Y.addVar(3); //在Y物件上,執行addVar成員函式 Y.showVar();

} }

7.5.2透過成員函式存取私用性資料變數

物件X   Var=11 物件Y   Var=6 物件Y   Var=9

執行結果

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

class CMyClass {

public void initVar() {

Var=1;

}

public void addVar(int b) {

Var=Var+b;

}

public void showVar() {

realShow(); //執行private等級的函式 }

private int Var;

private void realShow() {

System.out.println("Var=" + Var);

} }

不可被外部程式存取與執行,但 可被同類別的函式存取與執行。

7.5.2透過成員函式存取私用性資料變數

(31)

61

範例說明:

(1)第32~43行,定義了3個公用的成員函式,可以被外 部程式執行。第44~48行,宣告了1個私用的變數及定義 了一個私用成員函式,不可以被外部程式存取與執行。

(2)變數Var與成員函式realShow雖然不能夠被外部程式 存取與執行,但可以被同一類別的成員函式存取與執行

,例如第34、38、42行。

(3)程式執行時,記憶體內部呈現下列變化。

Step1:第10~11行執行完畢,記憶體中將同時存在兩個物件變 數X,Y以及它的實體。由於我們並未定義建構子,因此Java會 執行預設的建構子(不做任何事),並且所有的成員都會被自 動初始化。由於Var是int型態,故為0

7.5.2透過成員函式存取私用性資料變數

(下圖是記憶體的變化,由於尚未介紹static method,故省略main在記 憶體資料區的狀況)

7.5.2透過成員函式存取私用性資料變數

(32)

63

Step2:執行第13行,執行完畢時,X的成員變數Var被設定為1

。(下圖中,我們省略了部分的記憶體程式區)

7.5.2透過成員函式存取私用性資料變數

Step3:執行第14行,執行完畢時,Y的成員變數Var被設定為1

7.5.2透過成員函式存取私用性資料變數

(33)

65

Step4:執行第16行,執行完畢時,X的成員變數Var被設定為 11。

7.5.2透過成員函式存取私用性資料變數

Step5:執行第17~18行,執行完畢時,印出X的成員變數Var為 11。

Step6:執行第21行,執行完畢時,Y的成員變數Var被設定為6

7.5.2透過成員函式存取私用性資料變數

(34)

67

Step7:執行第22行,執行完畢時,印出Y的成員變數Var為6。

Step8:執行第25行,執行完畢時,Y的成員變數Var被設定為9。

Step9:執行第26行,執行完畢時,印出Y的成員變數Var為9。

7.5.2透過成員函式存取私用性資料變數

在範例7-2中,我們必須先呼叫initVar函式將成 員變數的初始值設定為1。是否有「更簡便」的方 式可以進行這樣的工作呢?

答案是有的

第一種方式是將成員變數於宣告時設定初始值(如範例 7-3)

另一種方式則是透過建構子來設定(如範例7-4)。

【觀念範例7-3】:改寫範例7-2,將成員變數設 定初始值,完成相同的初值設定效果。

範例7-3:ch7_03.java(隨書光碟 myJava\ch07\ch7_03.java)

7.5.2透過成員函式存取私用性資料變數

(35)

69 1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

/* 檔名:ch7_03.java 功能:成員變數設定初始值 */

package myJava.ch07;

import java.lang.*;

public class ch7_03 //主類別 {

public static void main(String args[]) {

CMyClass X = new CMyClass();

CMyClass Y = new CMyClass();

X.addVar(10); //在X物件上,執行addVar成員函式 System.out.print("物件X\t");

X.showVar();

System.out.print("物件Y\t");

Y.addVar(5); //在Y物件上,執行addVar成員函式 Y.showVar();

System.out.print("物件Y\t");

Y.addVar(3); //在Y物件上,執行addVar成員函式 Y.showVar();

} }

7.5.2透過成員函式存取私用性資料變數

執行結果:(同範例7-2)

範例說明:

第37行,Var成員變數被設定了初始值為1,因此不需要 initVar()函式,因為當物件實體產生時,物件的Var成 員就被設定為1。

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

class CMyClass {

public void addVar(int b) {

Var=Var+b;

}

public void showVar() {

realShow();

}

private int Var=1; //設定初始值 private void realShow()

{

System.out.println("Var=" + Var);

} }

7.5.2透過成員函式存取私用性資料變數

當物件實體產生時,物件的Var成 員就被設定為1。

(36)

71

在範例7-2中,我們必須先呼叫initVar 函式將成員變數的初始值設定為1。雖然 這個工作可以透過設定成員變數初始值 來完成(如範例7-3),但當我們希望初 始值與物件實體產生時有關(而非一個 單純的常數)時,就無法單單透過設定 成員變數初始值來完成了。

7.6 建構子

如果在產生物件實體時,能夠自動幫我 們呼叫某個特定函式,進行某些初始化 動作,不是使得設計程式方便多了嗎?

是的,Java如同其他的物件導向語言一般,也考 慮到了這一點,因此可以在建構子中來完成這些 事情。

建構子(Constructor)又稱建構函式,它 是生成物件實體時會自動執行的函式。

由於它很特別,因此定義語法與一般成 員函式有些不同,語法如下:

7.6 建構子

(37)

73

語法說明:

(1)建構函式名稱一定要和類別名稱相同。

(2)不可加上回傳值型態,連void也不行。如果加上回傳值型態(

含void)則不是建構函式,如此就不會在物件實體產生時自動執 行。

(3)沒有任何修飾字。

(4)建構函式只能被new運算子於產生物件實體時自動呼叫,無法 讓物件的一般函式自由呼叫。

(5)封裝等級一般宣告為public等級。如果宣告為private等級,

則只有其他建構函式可以呼叫它。無法被new自動呼叫。

【觀念範例7-4】:使用建構函式改寫範例7-2,使其成為建構函式以 便自動進行成員變數的初始化。

範例7-4:ch7_04.java(隨書光碟 myJava\ch07\ch7_04.java)

[封裝等級] 建構函式名稱(參數串) //建構函式名稱就是類別名稱 {

……建構函式內容……

}

7.6 建構子

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

/* 檔名:ch7_04.java 功能:建構函式 */

package myJava.ch07;

import java.lang.*;

public class ch7_04 //主類別 {

public static void main(String args[]) {

CMyClass X = new CMyClass(1); //自動呼叫建構函式 CMyClass Y = new CMyClass(2); //自動呼叫建構函式 X.addVar(10); //在X物件上,執行addVar成員函式 System.out.print("物件X\t");

X.showVar();

System.out.print("物件Y\t");

Y.addVar(5); //在Y物件上,執行addVar成員函式 Y.showVar();

System.out.print("物件Y\t");

Y.addVar(3); //在Y物件上,執行addVar成員函式 Y.showVar();

} }

7.6 建構子

(38)

75

執行結果:

範例說明:

第10行,宣告物件變數X並產生實體,當實體產生時,

就會自動立刻執行該類別的建構函式,也就是執行第 29~32行程式。第11行亦同理。如果將第11行傳入的引 數改為『1』,則執行結果同範例7-2。

26 27 28 29 30 31 32 : 46

class CMyClass {

public CMyClass(int i) {

Var=i;

}

...同範例7-2的第36~48行...

}

物件X Var=11 物件Y Var=7 物件Y Var=10

建構子名稱需與類別名稱相同。

建構子

7.6 建構子

上一章所提到的多載功能不但可以應用於一 般成員函式,也可以應用於建構函式。

程式可藉由定義多個不同參數(或不同資料型態)的 建構函式,提供物件實體初始化時更大的彈性。

【觀念範例7-5】:多載建構函式,使得在物件實體 產生時,可以依照不同情況執行不同的建構函式。

範例7-5:ch7_05.java(隨書光碟 myJava\ch07\ch7_05.java)

7.6.1建構子的多載

(39)

77

7.6.1建構子的多載

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

/* 檔名:ch7_05.java 功能:建構函式的多載 */

package myJava.ch07;

import java.lang.*;

public class ch7_05 //主類別 {

public static void main(String args[]) {

CMyClass X = new CMyClass();

CMyClass Y = new CMyClass(5,40);

CMyClass Z = new CMyClass(20.2,30.6);

X.showVar();

Y.showVar();

Z.showVar();

} }

class CMyClass {

public double VarA;

private double VarB;

呼叫第24行的建構子

呼叫第29行的建構子

呼叫第34行的建構子

執行結果:

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

public CMyClass() //定義無參數的建構函式

{

VarA=10;

VarB=10;

}

public CMyClass(int a,int b) //定義兩個整數參數的建構函式 {

VarA=a;

VarB=a+b;

}

public CMyClass(double a,double b) //定義兩個浮點數參數的建構函式 {

VarA=a;

VarB=a*b;

}

public void showVar() {

System.out.println("VarA=" + VarA);

System.out.println("VarB=" + VarB);

} }

7.6.1建構子的多載

VarA=10.0 VarB=10.0 VarA=5.0 VarB=45.0 VarA=20.2 VarB=618.12

(40)

79

範例說明:

(1)第24~38行是3個建構函式的定義,其署名不相同,

符合多載的規定。並且其內的建構內容也可以不同。

(2)第10行產成X物件實體,由於沒有引數,所以會執行 第24~28行的建構函式。第11行生成Y物件實體,由於有 2個int整數引數,所以會執行第29~33行的建構函式。

第12行生成Z物件實體,由於有2個double浮點數引數,

所以會執行第34~38行的建構函式。

7.6.1建構子的多載

如果您未定義建構函式,則Java編譯器會自動產 生一個內建的預設建構函式(Default

Constructor)到程式中,函式內容為空,也沒有 參數,格式如下:

正因為有此預設的建構函式,所以在之前的範例中,有 時我們並未定義建構函式,但編譯仍不會出錯。

但是如果您已經定義了一個以上的建構函式時,Java編 譯器便會認定您已經準備好所有的建構函式,此時將不 會再產生預設建構函式,而若您沒有建立一個無參數的 建構函式,卻又於產生物件實體時不傳入引數,則會發 生錯誤,請見以下的範例。

7.6.2 預設的建構子

public 類別名稱() //預設的建構函式 {

}

(41)

81

【觀念範例7-6】:取消預設建構函式的注意事項 範例7-6:ch7_06.java(隨書光碟

myJava\ch07\ch7_06.java)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

/* 檔名:ch7_06.java 功能:預設的建構子 */

package myJava.ch07;

import java.lang.*;

public class ch7_06 //主類別 {

public static void main(String args[]) {

CMyClass X = new CMyClass(5,40); //實體產生有兩個整數引數 //CMyClass Y = new CMyClass();

X.showVar();

//Y.showVar();

} }

7.6.2 預設的建構子

無法找到對應的建構子 VarA=5

VarB=45

執行結果

範例說明:

第11行的註解如果 取消,則會發生錯 誤,因為預設的建 構函式並不會產生

,故找不到對應的 建構函式可執行。

所以在定義建構函 式時,應該先定義 一個無參數的建構 函式(函式內容可 以暫時為空),以 避免這樣的狀況。

7.6.2 預設的建構子

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

class CMyClass {

public int VarA;

private int VarB;

public CMyClass(int a,int b) //定義兩個整數參數的建構函式 {

VarA=a;

VarB=a+b;

}

public void showVar() {

System.out.println("VarA=" + VarA);

System.out.println("VarB=" + VarB);

} }

小試身手7‐1

依照範例7‐6的說明,將第11、13行的註解取消,並加入無參數的建構函式(函式 內容為空),使得可編譯並執行成功。

小試身手7‐1

依照範例7‐6的說明,將第11、13行的註解取消,並加入無參數的建構函式(函式

內容為空),使得可編譯並執行成功。

(42)

83

在Java中,想要釋放物件實體,只要將 所有參考到該物件實體的物件變數都設 為null或指向其他實體,則Java的回收 機制就會在一段時間後自動回收該物件 實體的記憶體空間。

而其他程式語言(例如C++)則提供了delete與解 構函式,需要使用者手動關心物件釋放的後續動 作。

7.6.3回收物件實體

在呼叫函式時,引數也可以是物件

在前面的範例中,我們曾經示範過傳遞字串物件

。當時我們曾經提及,傳遞字串物件,事實上採 用的是傳「參考值」呼叫

因為物件變數本身是一個參考而非實體。

當我們要傳遞一個自行定義的類別所宣告的物件 變數時,同樣採用的是傳「參考值」呼叫,因此

,呼叫端與被呼叫端可以共用一個物件實體。

在下面這個範例中,我們將傳遞物件,並且將使 用成員函式多載進行不同資料型態的加法。

7.7物件引數的傳參考值呼叫

(43)

85

【觀念範例7-7】:使用多 載設計成員函式,並且接 受引數為其他類別的物件

。在本範例中,我們將把 加法函式擴充到二維向量 的加法,範例如右:

範例7-7:ch7_07.java(

隨書光碟

myJava\ch07\ch7_07.java

7.7物件引數的傳參考值呼叫

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

/* 檔名:ch7_07.java 功能:傳遞物件與接收物件 */

package myJava.ch07;

import java.lang.*;

public class ch7_07 //主類別 {

public static void main(String args[]) {

CVector2 i = new CVector2();

i.set(20,40);

CVector2 j = new CVector2();

j.set(15,45);

CVector2 k; //k是CVector2物件變數,在第16行用來接收回傳值 CMyClass X = new CMyClass();

k=X.sum(i,j);

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

System.out.println("Vector k=i+j=(" + k.x + "," + k.y + ")");

} }

7.7物件引數的傳參考值呼叫

Vector i=(20,40) Vector j=(15,45) Vector k=i+j=(35,85)

執行結果

(44)

87

7.7物件引數的傳參考值呼叫

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

class CMyClass {

public int sum(int a,int b) {

return a+b;

}

public double sum(double a,double b) {

return a+b;

}

public CVector2 sum(CVector2 a,CVector2 b)//二維向量的加法成員函式 {

CVector2 tempVector = new CVector2();

tempVector.x=a.x+b.x;

tempVector.y=a.y+b.y;

return tempVector;

} }

class CVector2 //二維向量類別 {

public int x,y; //二維向量的兩項元素資料

public void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} }

範例說明:

(1)我們在第43~51行,宣告了另一個類別CVector2,用來代 表二維向量。並提供一個set成員函式在第46~50行。

(2)除了整數與浮點數的加法,在第34~40行,我們又增加了 CMyClass的另一個成員函式sum,它接受的引數是兩個

CVector2類別的物件,並且回傳一個CVector2類別的物件,

它會做二維向量的加法,並回傳二維向量。

(3)第10~11行,宣告一個CVector2類別的物件i並產生實體

,且設定成員變數為(20,40)。

(4)第12~13行,宣告一個CVector2類別的物件j並產生實體

,且設定成員變數為(15,45)。

(5)第14行,宣告一個CVector2類別的物件變數k。

(6)第15行,宣告一個CMyClass類別的物件X並產生實體。

7.7物件引數的傳參考值呼叫

(45)

89

(7)第16行,透過X物件的sum成員函式,幫我們做二維 向量的加法,所以k向量=(35,85)。

(8)在第16行的函式呼叫,i物件的參考傳送給對應的a 物件參考,故i與a實際上指向同一個物件實體。j與b亦 同理。

(9)在第14行時,k是一個物件參考,初始值為null,而 第16行時,由於第39行會回傳一個物件實體位址,所以 k儲存該位址後,可以指向物件的實體。

在上一個範例中,雖然i與a指向同一個物件,j與 b指向同一個物件,但依據的是傳「參考值」呼叫

對於Java的傳「參考值」呼叫,每一年都會引起論壇的 討論,爭辯傳參考呼叫與傳參考值呼叫的差別。

7.7物件引數的傳參考值呼叫

傳參考呼叫(Pass by reference)一般而言,指的是程式語言 功能中的傳址呼叫(Call by address)

其特色是被呼叫端的參數會影響呼叫端的引數,如C++與C#都提 供此一功能。

傳值呼叫(Pass by value)則是程式語言功能中的傳值呼叫 (Call by value)

其特色是被呼叫端的參數不會影響呼叫端的引數,大多數的程 式語言都提供了此一功能,如Java。

由於傳址呼叫所能達到的效果,在被呼叫端需要影響呼叫端 兩個以上的變數時非常有用(因為函式回傳值只能傳遞一個 值),因此程式語言必須提供傳址呼叫

若未提供傳址呼叫,則必須提供其他機制來達到同樣的效果 而所謂其他機制,則不可避免地仍需要傳遞一個記憶體位址才 可能達到同樣的效果

例如C語言就是透過傳指標呼叫(Pass by pointer)來達成此一效果。

7.7物件引數的傳參考值呼叫

(46)

91

Java的傳「參考值」呼叫則與C語言的傳指標呼叫(Pass by pointer)類似

它雖然也是透過引數傳遞一個位址給被呼叫端的參數

,但即便參數所保存的位址值(參考值)改變了,也 不會影響引數所保存的位址值(參考值)。

換句話說,傳「參考值」呼叫仍屬於傳值呼叫(Call by value)的一種,只不過這個值是一個參考罷了。

所以傳參考呼叫與傳參考值呼叫是不同的兩種機制。

因此,在眾多的Java原文技術文件中,都會提及,Java只支 援傳值呼叫,這是因為傳「參考值」呼叫仍屬於傳值呼叫 (Call by value),而不是傳址呼叫(Call by address)。

以上,在上一章介紹Java傳遞陣列時,我們已經說明過 了,下面這個範例,將會證明Java在傳遞物件或陣列時

,採用的只是傳「參考值」呼叫,而非傳參考呼叫。

7.7物件引數的傳參考值呼叫

假設Java提供了傳參考呼叫(傳址呼叫

),我們可以很容易的製作一個

swap(i,j)函式,用以將i,j互相對調。

以下,我們嘗試製作一個swap函式將兩個引數(物 件參考i,物件參考j)對調看看。

【觀念範例7-8】:嘗試設計一個swap函式互換兩 個物件參考。

範例7-8:ch7_08.java(隨書光碟 myJava\ch07\ch7_08.java)

7.7物件引數的傳參考值呼叫

(47)

93 1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

/* 檔名:ch7_08.java 功能:物件的傳參考值呼叫 */

package myJava.ch07;

import java.lang.*;

public class ch7_08 //主類別 {

public static void main(String args[]) {

CVector2 i = new CVector2();

i.set(20,40);

CVector2 j = new CVector2();

j.set(15,45);

System.out.println("---原始---");

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

CMyClass X = new CMyClass();

X.swap(i,j);

System.out.println("---swap後---");

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

} }

7.7物件引數的傳參考值呼叫

‐‐‐‐‐原始‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

‐‐‐‐‐swap後‐‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

執行結果

範例說明:

(1)從執行結果中,我們發現對調失敗了,為什麼會這樣呢

?我們以記憶體的變化來說明。

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

class CMyClass {

public void swap(CVector2 a,CVector2 b) //互換函式 {

CVector2 temp;

temp=a;

a=b;

b=temp;

} }

class CVector2 //二維向量類別 {

public int x,y; //二維向量的兩項元素資料

public void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} }

7.7物件引數的傳參考值呼叫

(48)

95

(2)當第10~17行執行完畢時,記憶體內容呈現下圖狀況

,i指向3100的物件實體,j指向3600的物件實體。

7.7物件引數的傳參考值呼叫

(3)第18行的X.swap(i,j);會把i與j的參考傳送給a與b

,如下圖。然後開始執行第28~33行的程式。

7.7物件引數的傳參考值呼叫

(49)

97

(4)第29行會配置一個可存放參考的記憶體給temp,第 30行則會讓a的參考設定給temp,使得兩者都指向同一 個物件。第30行執行完畢時,結果如下圖:

7.7物件引數的傳參考值呼叫

(5)第31行則會讓b的參考設定給a,使得兩者都指向同 一個物件。第31行執行完畢時,結果如下圖:

7.7物件引數的傳參考值呼叫

(50)

99

(6)第32行則會讓temp的參考設定給b,使得兩者都指向 同一個物件。第32行執行完畢時,結果如下圖:(a與b 的參考已經互換完成)

7.7物件引數的傳參考值呼叫

(7)第33行執行完畢時會返回函式呼叫處,此時,由於 參數a,b及函式內的temp都屬於區域變數,故會被釋放

。故a與b的互換根本沒有意義,因為i與j並沒有互換,

記憶體內容和執行函式呼叫前是完全相同的。所以印出 的結果自然是沒有互換成功的結果。

(8)雖然我們互換失敗,但確實在swap中,若透過a或b 或temp修改物件實體的變數內容的話,則呼叫返回後,

i或j的物件實體內容也會被更動。但這並非傳統所謂的 傳參考呼叫(pass by reference),頂多只能稱的上是 傳參考值呼叫(pass by value of reference)。

(9)如果您熟悉C++語言的話,就會知道,C++語言支援 傳參考呼叫,一個類似功能的程式碼如下,請注意其結 果,i與j的內容是被更動的。

7.7物件引數的傳參考值呼叫

(51)

101 1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

/* 檔名:ch7_08.cpp 功能:傳參考呼叫 */

#include <iostream>

#include <stdlib.h>

using namespace std;

class CVector2 //二維向量類別 {

public: int x,y; //二維向量的兩項元素資料

public: void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} };

class CMyClass {

public: void swap(CVector2 &a,CVector2 &b) //互換函式,傳參考呼叫 {

CVector2 temp;

temp=a;

a=b;

b=temp;

} };

7.7物件引數的傳參考值呼叫

‐‐‐‐‐原始‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

‐‐‐‐‐swap後‐‐‐‐‐‐‐

Vector i=(15,45) Vector j=(20,40)

執行結果

(10)不只C++支援傳參考呼叫,C#同樣也支援傳參考呼叫,上述範 例改寫為C#版本的程式碼如下,請注意其結果,i與j的內容是被 更動的。

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

int main(char args[]) {

CVector2 i;

i.set(20,40);

CVector2 j;

j.set(15,45);

cout << "---原始---" << endl; //列印

cout << "Vector i=(" << i.x << "," << i.y << ")" << endl;

cout << "Vector j=(" << j.x << "," << j.y << ")" << endl;

CMyClass X;

X.swap(i,j); //互換i,j

cout << "---swap後---" << endl; //列印

cout << "Vector i=(" << i.x << "," << i.y << ")" << endl;

cout << "Vector j=(" << j.x << "," << j.y << ")" << endl;

system("pause");

return 0;

}

7.7物件引數的傳參考值呼叫

參考文獻

相關文件

在進口指數方面,按經濟貨物大類(CGCE)作分類計算,包括消費品、原料及半製成品、燃

在進口指數方面,按經濟貨物大類(CGCE)作分類計算,包括消費品、原料及半製成品、燃

在進口指數方面,按經濟貨物大類(CGCE)計算,包括消費品、原料及半製成品、燃料及潤

FUNCTION ODD_par8 (DI: STD_LOGIC_VECTOR(7 DOWNTO 0) ) RETURN STD_LOGIC;. FUNCTION ODD_par81 (DI: BIT_VECTOR(7 DOWNTO 0))

z在 salary 屬性定義中,不設定 set ,並且 get 回傳值為 baseSalary 加上

zSELECT 欄位名稱1, 欄位名稱2, … FROM 資料表名稱 WHERE 條件式 ORDER BY 欄 位名稱 (字串需以單引號 '

• 本章動畫的主角是各個英文字母文字物件,由 於 Flash 提供了文字物件打散 (Break Apart) 及分散至圖層 (Distribute to Layer)

級別級別描述學習成果學生表現示例 I3.2 他們開始對人、事件及物件