總結之前的內容,物件(object)指涉某一事物,類別(class)指代象的類型。物件可以有狀態和動作,即資料成員和方法。
到目前為止,資料成員和方法都是同時開放給內部和外部的。在物件內部,我們利用this來呼叫物件的資料成員和方法。在物件外部,例如當我們在另一個類別中呼叫物件的時,可以使用物件.資料成員和物件.方法() 來呼叫物件的資料成員和方法。
我們將要封裝(encapsulation)物件的成員(成員包括資料成員和方法),從而只允許從外部呼叫部分的成員。利用封裝,我們可以提高物件的易用性和安全性。
封裝與介面
封裝(encapsulation)是電腦常見的術語,即保留有限的外部介面(interface),隱藏具體實作細節。例如在Linux架構,就可以看到Linux作業系統封裝了底層硬體的具體細節,只保留了系統呼叫這套介面。用戶處在封裝的外部,只能透過接口,進行所需的操作。
封裝在生活中很常見。例如下面是一個充電電筒:
一個使用者即使不看說明書,也可以猜到這個電筒的操作: 開關和充電。這個電筒用一個塑膠殼將用戶不需要接觸的內部細節隱藏起來,只保留了兩個接口,開關和電插頭。使用這兩個接口,使用者足以使用該產品在設計中想要實現的功能。如果所有的細節都同時暴露給用戶,那麼用戶會對產品感到不知所措(例如下面不加殼的遙控器)。因此,封裝提高了產品的易用性。
如果產品不封裝,手電筒或遙控器的許多細節會暴露在使用者面前: 電池、電路、密封的橡膠等等。儘管這可以讓使用者更自由的對產品實施操作,例如直接給電池放電,取出一個LED燈等等。然而,使用者往往要承擔更大的損壞產品的風險。因此,封裝提高了產品的安全性。
一個Java軟體產品與一個日常產品相同。一個物件內部可以有許多成員(資料成員和方法)。有一些資料成員和方法只是內部使用。這時,我們會希望有一個給物件「加殼」的機制,從而封裝物件。這樣,使用者可以比較容易學習和使用外部的接口,而不必接觸內部成員。
對象成員的封裝
Java透過三個關鍵字來控制物件的成員的外部可見性(visibility): public, private, protected。
1.public: 該成員外部可見,即該成員為介面的一部分
2.private: 該成員外部不可見,只能用於內部使用,無法從外部存取。
(protected涉及繼承的概念,放在以後說)
我們先來封裝先前定義的Human類別:
複製代碼代碼如下:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human(160);
System.out.println(aPerson.getHeight());
aPerson.growHeight(170);
System.out.println(aPerson.getHeight());
aPerson.repeatBreath(100);
}
}
class Human
{
/**
* constructor
*/
public Human(int h)
{
this.height = h;
System.out.println("I'm born");
}
/**
* accessor
*/
public int getHeight()
{
return this.height;
}
/**
* mutator
*/
public void growHeight(int h)
{
this.height = this.height + h;
}
/**
* encapsulated, for internal use
*/
private void breath()
{
System.out.println("hu...hu...");
}
/**
* call breath()
*/
public void repeatBreath(int rep)
{
int i;
for(i = 0; i < rep; i++) {
this.breath();
}
}
private int height; // encapsulated, for internal use
}
內部方法並不受封裝的影響。 Human的內部方法可以呼叫任意成員,即使是設定為private的height和breath()
外部方法只能呼叫public成員。當我們在Human外部時,例如Test中,我們只能呼叫Human中規定為public的成員,而不能呼叫規定為private的成員。
透過封裝,Human類別就只保留了下面幾個方法作為介面:
1.getHeight()
2.growHeight()
3.repBreath()
我們可以將Human類別及其介面表示為如下圖的形式:
“加了殼的遙控器”
如果我們從main強行呼叫height:
複製代碼代碼如下:
System.out.println(aPerson.height);
會有以下錯誤提示:
複製代碼代碼如下:
Test.java:6: height has private access in Human
System.out.println(aPerson.height);
^
1 error
Beep, 你觸電了! 一個被說明為private的成員,不能被外部呼叫。
在Java的通常規格中,表達狀態的資料成員(例如height)要設定成private。資料成員的修改要透過介面提供的方法進行(例如getHeight()和growHeight())。這個規範起到了保護資料的作用。使用者不能直接修改數據,必須透過相應的方法才能讀取和寫入數據。類別的設計者可以在介面方法中加入資料的使用規範。
類的封裝
在一個.java檔案中,有且只能有一個類別帶有public關鍵字,例如上面的Test類別。所以,從任意其他類別中,我們都可以直接呼叫該類別。 Human類沒有關鍵字。更早之前,我們物件的成員也沒有關鍵字。這種沒有關鍵字的情況也代表了一種可見性,我將在套件(package)的講解中深入。
練習封裝一個Torch類,來表示電筒。接口有開關和充電。內部的成員有電量。
總結
封裝,介面
private, public