封裝
所謂物件導向程式,主要是由許多不同功能的物件組合而成,而其中的組合關係可能相當的錯綜復雜,若每個物件之間的變數都能夠直接任意的存取使用,很容易就會造成物件內部參數的錯亂,為了避免這類的情況發生,最好的方式就是將物件內的變數都封裝起來,並提供一個單一對外存取的窗口,任何物件要存取這個變數都必須透過這個窗口,而這個窗口就是利用函式(方法)來包裝變數,如此一樣便能讓程式運作更加的穩定與安全。事實上物件裡所包含的結構及方法,本身也是透過物件包裝起來,這也算是封裝特性的一種。所以我們可以利用封裝的特性將物件實作的方法與介面分開,防止物件本身的一些屬性被使用者無意間的修改。
- 類別成員(Class member)
在類別中可定義Field成員及方法(Method) 成員。
package com.ittraining.example; |
---|
在Java中,類別的存取權限修飾詞有"public"、"protected"、"private"、"default(無條飾詞)",當修飾詞或空時,預設將會以以套件 (package)為可存取範圍,也就是說只有在同一層package中的class才能進行存取。
上述的範例程式中程式中,我們定義了一個Student類別,其中包含3個被宣告為private的field成員,分別是classID與name。由於宣告為private,所以代表這3個成員只能在同一個Object中進行存取,而無法別其他物件直接進行存取。
接著我們定義了6個Method成員,全部都是宣告為"public",代表這些method是公開的,可被其他物件所呼叫,method的定義方式如下 :
存取修飾 傳回值型態 方法名稱(參數列) { return 傳回值;} |
---|
也就是我們將classID與name成員變數透過private宣告進行封裝,並另外在建立四個公司的方法讓其他物件透過我們公開的方法去對classID與name進行存取,而非直接將classID與name成員公開出去,一切的資料存取都需透過method成員呼叫,而非直接呼叫該field資料成員來進行存取。我們稱之為“資料的封裝”,封裝的好處在於可以確保資料的安全性,與存取介面的一致性。
- 靜態成員(Static member)
對於每個物件來說,每個物件都各自擁有自己的資料成員。就算兩個物件是由同一個類別透過new關鍵字產生,也不會發生更改了A Object內的成員變數,連帶著B Object也同時變動的情況。因為就在Object被class loader載入的那一刻,資料成員就以復本(Copy)的型態載另到記憶體之中。
然而在某些時後你會想要這些物件都可以共享相同的資料成員,此時就需要將刻資料成員宣告為static。一但資料成員被宣告為static後,在同一個類別之中,該資料就只會存在一份,object被class loader載入時並不會在copy static 成員到記憶體中,而是共享的。舉個例來說,我們將前一個Student的範例中進行些小修改,填加幾個預設學校名稱的參數,並宣告為static。
package com.ittraining.example; |
---|
由於static成員是直接屬於類別,並不屬於任一個物件所獨有,因此可以直接以類別名稱去進行存取,而無需實體化成物件。例如:
st1.setSchoolName(Student.SCHOOL_ONE);
即有靜態資料,相對的我們也可以將方法宣告成靜態的方法(static method),而靜態方法通常是為了提供一些簡單的常數運算或一些操作上的小工具,例如我們可以於Student.java中新增一個help的靜態方法,該方法可說明此類別的工能與使用方式。
略 |
---|
靜態方法的呼叫方式與靜態資料相同,皆是直接透過類別名稱+.運算子 + 方法名。其實我們時常用到System.out.println()本身就是一個靜態的資料+靜態方法的應手,這也就是為什麼我們不必去new System物件就可以宜接使用out這個靜物件,並可以直接呼叫println()這個靜態方法將字串貼於console上.
由於static成員是屬於類別而不是物件,因此若你要在static method中呼叫非static 資料,在編譯時就會出現以下的錯誤訊息:non-static variable xxx cannot be referenced from a static context
同樣的你也不能在static method中乎叫非static的method
non-static method xxxx() cannot be referenced from a static context
由於static成員是屬於類別而不是物件,所以當您呼叫static方法時,並不會傳入物件的位置參考,所以static方法中不會有 this參考,由於沒有this參考,所以在Java的static方法成員中不允許使用非static成員,因為程式沒有this來參考至物件位址,也 就無法辨別要存取哪一個物件的成員,事實上,如果您在static方法中使用非static資料成員,在編譯時就會出現以下的錯誤訊息:
non-static variable test cannot be referenced from a static context
或者是在static函式中呼叫非static函式,在編譯時就會出現以下的錯誤訊息:
non-static method showMe() cannot be referenced from a static context
最後如果你希望在載入類別時就先進行一些初始化的動作,此時你可以使用static區塊,將所有需要做初始化資源的成員全都放在static區塊中。Static區塊會在第一次呼叫而被載入時,static區塊中的程式碼會先被執行,且只會執行一次。
public class Student { .... static { // 初始化程式碼 } .... } |
---|
- 建構方法(Constructor)
當一個物件被載入到memory時,會先呼叫該物件的建構方法,而建構方法主要可用來做物件的初始化動作。另外Java的語法中並不像C有解構方法(Destructor),而是交由垃圾回收器自動幫我們做處理。建構方法與一般方法不同處在於建構方法並無回傳質,而每一個物件一定必須要有建構方法,若你沒有特別撰寫建構方法,系統會使用預設的建構方法(無任何傳入質)進行物件的初始化。我們可將Student範例在進行改寫,填入建構方法來為物件做初始化的動作。
package com.ittraining.example; |
---|
- 重載(Overload)方法
Java支援方法「重載」(Overload),又有人譯作「超載」、「過載」,這種機制為類似功能的方法提供了 統一的名稱,但是根據參數列型態的不同,而自動呼叫對應的方法。一個例子可以從 java.lang.String 類別上提供的所有方法看到,像是它的valueOf()方法就提供了多個版本:
static String valueOf(boolean b)**static String valueOf(char c)static String valueOf(char[] data)static String valueOf(char[] data, int offset, int count)static String valueOf(double d)static String valueOf(float f)static String valueOf(int i)static String valueOf(long l)**static String valueOf(Object obj)
雖然呼叫的方法名稱都是valueOf(),但是根據所傳遞的引數資料型態不同,您可以呼叫不同版本的方法來進行對應的動作。方法重載的功能使得程式設計人員能較少苦惱於方法名稱的設計,以統一的名稱來呼叫相同功能的方法,方法重載不僅可根據傳遞引數的資料型態不同來呼叫對應的 方法,參數列的參數個數也可以用來設計方法重載。方法重載時可以根據方法參數列的資料型態,也可以根據參數的個數,不過必須注意的是,方法重載不可根據傳回值的不同來區別。 方法重載當被使用於物件導向設計時的建構方法的使用時,提供物件生成時不同的建構方法,或者是使用於物件所提供的同名方法,但多樣化的參數設定方式。
最後我們設計了一個Account範例來為這一章做個總節。
class Account{ |
---|
【執行結果】
圖 16 封裝範例執行結果
我們利用關鍵字private宣告了一個私有的變數money用來記錄存款金額。由於變數宣被告成private,因此只有在Account物件本身自己可以存取此變數,其他物件皆無權限存取該變數,這就意味者我們己經將變數封裝於Account物件之中。接下來我們要建立一個公開函式,做為其他物件存取money變數的窗口,請注意到getMoney函式必須宣告為public,如此一來任何的物件都可以呼叫此函式,並取得Money質。
由於存款金額是一個相當重要的資訊,雖然我們己經利用資料封裝的特性,禁止其他物件直接存取money變數,但若被封裝的資訊只願意被分享給特定物件存取時又該如何設計呢?答案其實很簡單,竟然我們只限制所有物件都必須透過getMoeny函式存取money變數,那我們一樣可以利用這一個單一的存取窗口來進行身分檢查的動作,接著就讓我們為此範例程式加入密碼檢查的功能:
class Account{ |
---|
在Account裡我們新增了一個passWorld變數用來存放密碼,並在getMoney()函式中增加了一個密碼檢查的功能,在這裡密碼預設為123,只要密碼正確就會傳回money質。這個範例程式中還保留有許多可以擴充的功能,如撰寫一個設定密碼的函式,與存入存款的函式,這些都是相當不錯的練習題材,讀者可以動手練習撰寫看看。