Ch.11 定義、使用類別、繼承

11-1 定義類別

因為 Java 語言是支援物件導向的程式語言,所以 Java 程式設開發人員最主要的目標之一,就是設計許多的物件來建立系統,或是更具體地解決一個問題,到目前為止,您已經接觸過幾個 Java SE 中的幾個物件,像是陣列、java.lang.String、java.lang.Integer、java.lang.Double 等,這些物件攜帶著比基本資料型態更多的資訊,藉由這些物件之間的交互作用,您可以解決系統所面對的問題。

Java SE 所提供的類別之目的,在於解決程式開發人員常面對的一些問題,讓開發人員無需花費時間精力重複設計這些類別,而是以這些類別為基礎,進一步解決開發人員所面對的需求,而為了解決問題,自行設計類別是必要且基本的,開發人員必須從問題中辨識出各種物件、定義一個類別以在程式中表示這些物件、然後讓這些物件彼此交互以解決問題。

• 物件導向分析入門

物件導向分析( Object-Oriented Analysis, OOA )是從問題中識別出各個物件、識別出物件的屬性與行為、並設計物件之間交互行為的一種分析方法。Java 語言既然是一種物件導向程式語言,了解基本的物件導向分析方法,對於學習Java 的初學者來說,就成了一個必備的基本能力。

識別物件

為了決定問題範圍中的物件,您必須能夠先辨識物件的特性:

  • 物件可以是實質性或是概念性 – 顧客的帳目就是一個概念性物件的例子,因為您無法實際接觸,然而,自動提款機(Automated teller machine, ATM)每天都被許多人使用,因此它是實質性物件的例子。
  • 物件擁有屬性(特徵),例如大小、名稱、形狀等。例如,某一物件可能擁有一個顏色的屬性。物件所有屬性的值通常被稱為物件現有的狀態。例如:一個物件可能有一個值為紅色的顏色屬性和一個值為大的尺寸屬性。
  • 物件擁有操作( Operation )(它們能做的事),例如設操作、行為定一個值、顯示螢幕、增加速度。操作(Operation)通常會影響物件的屬性。物件所執行的操作通常被稱為此物件的行為( Behavior )。例如,某一物件可能會有一個運算讓其它物件將它的顏色屬性從一狀態改變到另一狀態,例如從紅色變成藍色。

重點提示

物件的名字通常是像"帳目"或"襯衫"的名詞,物件的屬性通常也是名詞,例如:"顏色"或"尺寸"。物件的運算通常是動詞,或名詞與動詞的結合,例如:"顯"或"提交訂單"。

當您利用物件導向分析著手處理一個問題時,您對辨別周遭事物的能力將幫助您更容易定義出物件,下圖表現出一個Duke 為物件時的特性:

圖11-1 擁有屬性與操作的實質物件

設計類別

辨別物件幫助您更容易為問題域中的物件設計類別( Classes ),或是系統內每一個物件的藍圖。舉例來說,窗戶的製造商通常會為他們將製造的窗戶的樣式設計一個藍圖。這些藍圖定義了在購買時可供選擇的顏色和把手樣式。這些藍圖是各種不同顏色和樣式的窗戶依據的基礎。

在物件導向的術語中,利用類別 (一般的藍圖)所建立的每一個物件 (窗戶),被稱為類別的實體( Instance )。更具體地來說,每一個由類別而建立的物件,其每一個屬性可以擁有特定的狀態(值),但是將有相同的屬性和操作行為。

在 American Heritage 字典中,把類別(class)定義為「有某些共同屬性的團體」(“A group whose members have certain attributes in common.”)。

封裝

封裝( Encapsulation )是一個物件導向程式的術語,表示物件中資料的藏匿處(一個安全的「容器」),使得這些資料只能透過某些方法來取得。封裝的重要性在於使您的類別較容易被其他的程式設計人員使用,並且避免類別中的某些資料遭受不適當的修改。

下圖利用保險箱表示了一個封裝的概念。其擁有了一個公開的介面(一個密碼鎖),只有正確的操作,才允許存取私用的內容。

圖11-2 封裝是設計上的一種建議

對物件的私用資料予以封裝是一個建議,而非實作時必要的作法,在設計上的作法是:對於物件的私用資料,能不直接公開就不直接公開,目的在達到更好的物件權限控制,以提高程式的安全性與穩定性。

• 用 Java 語言來定義類別

在 Java 程式語言中,使用關鍵字 class 來定義類別,類別主要包括兩個部份:類別宣告(Class declaration)與類別本體(Class body)。宣告類別的語法為:

[modifier] class class_identifier {
    // ..類別本體
}
                            

其中:

  • modifier 被使用來決定此類別相對於其它類別的存取權。modifier 是可有可無的(以方括弧表示),可能是 publicabstract、或是 final
  • class 關鍵字告訴編譯器此程式區塊為一類別宣告。
  • class_identifier 是您給予類別的名稱。

您可以使用 public 來修飾類別的權限,表示該類別是一個公開的類別,程式中的任何物件或類別中都可以直接引用該類別,到目前為止您所看到的類別也都是設定為公開的,並且一個公開的類別在檔案主檔名部份,必須與類別的名稱是一致的。

在類別本體中主要包括三個部份:變數( Variable )建構式( Constructor )方法( Method )。直接以實例來說明,程式碼 11-1 定義了一個 Hello 類別,當中包括了類別本體的三個主要部份:

public class Hello{
    private String name;

    public Hello(){
        name = "nobody";
    }

    public Hello(String one){
        name = one;
    }

    public void hello(){
        System.out.print("Hello ");
        System.out.println(name);
    }

    public void setName(String one){
        name = one;
    }
}
                            
程式碼 11-1 Hello.java 變數

程式碼 11-1 第 2 行定義了類別中的變數( Variable ),又常稱為值域( Field )資料成員( Data member ),變數即之前物件導向分析入門所介紹的,表示物件的屬性( 特徵 ):例如大小、名稱、形狀等。例如,某一物件可能擁有一個顏色的屬性。物件所有屬性的值通常被稱為物件現有的狀態。

程式碼第 2 行宣告了一個 name 變數,用來表示 Hello 型態物件所擁有的一個屬性,name 的型態是 String,預設不參考任何物件,在類別本體中宣告變數的話,依資料型態之不同而會有不同的預設值,如表 11-1 所示:

資料型態初始值
byte0
short0
int0
long0L
float0.0F
double0.0D
char\u0000
booleanfalse
Objectnull
表 11-1 類別變數之預設值

類別變數 name 在宣告時使用 private 關鍵字修飾,表示這個類別變數只能在物件內部被存取,具體而言就是只能被在類別本體中定義的方法(Method)存取,外部物件無法直接存取 name,這樣的資料成員稱之為私用成員( Private member ),外部物件只能透過公開的(Public)操作介面才可以間接存取到私用成員(如果類別中有定義這樣的操作介面)。

圖11-3 透過公開方法存取私用成員

類別變數宣告時的權限修飾是可有可無的,如果不設定的話,預設是在同一個套件管理下的類別都可以存取該類別變數。

建構式

建構式( Constructor )是與類別名稱具有相同名稱的特殊方法,它不具有返回值( Return value ),程式碼 11-1 的第 4 行到第 6 行定義了無參數的建構式,第 8 行到第 10 行定義了可接受 String 型態之引數的建構式,建構式會在您使用關鍵字 new 產生一個實例時執行,至於執行哪一個建構式,視您實例化時給定哪一種型態的引數而決定。

例如下面的程式碼片段會建立一個 Hello 類別的實例,程式在執行時發現您實例化時並沒有提供引數,因此實例化時會執行第 4 行到第 6 行所定義的無參數建構式,將 name 參考至"nobody"字串:

Hello hello = new Hello();
                            

下面的程式碼片段會建立一個 Hello 類別的實例,程式在執行時發現您實例化時提供了"Java"作為引數,因此實例化時會執行第 8 行到第 10 行所定義的建構式,因此將 name 參考至"Java"字串實例:

Hello hello = new Hello("Java");
                            

沒有任何參數的建構式稱之為預設建構式 ( Default constructor ),如果您在定義類別的時候,並沒有定義任何的建構式,則在進行編譯時,編譯器會自動為該類別提供一個公開的、無參數的的預設建構式,如果您定義了自己的建構式,則編譯器就不會為您產生預設建構式,此時由於沒有預設建構式,所以您使用 new 建立實例時,一定要使用有參數的建構式。

重點提示

如果您定義了有參數的建構式,即使您不使用預設建構式,在設計上仍建議撰寫一個本體為空的(不包括任何陳述句)的預設建構式。

在建構式前使用了 public 來修飾,表示這是個公開的建構式,任何物件都可以直接實例化該類別,權限修飾是可有可無的,如果省略的話,預設是只有同一套件管理下的物件可以實例化該類別。

方法

程式碼 11-1 的第 12 行到第 15 行定義了 hello() 方法(Method),第 17 行到第 19 行定義了 setName()方法,方法是物件擁有的操作能力(Operation),也可表現出物件的行為(Behavior),例如您可以這麼實例化一個 hello 物件,並操作它的 hello()方法:

Hello hello = new Hello("Java");
hello.hello();
                            

程式會執行 hello()中所定義的方法,結果是在螢幕上顯示"Hello Java"的文字。

方法可以返回一個值,表示方法執行完畢後的運算結果,如果撰寫為 void,表示這個方法執行完畢後不必返回任何值。

方法封裝了物件執行某些操作或演算的細節,一個方法被public 關鍵字修飾時,表示它是一個公開的方法,可以被所有的物件直接操作。

方法也可以用 private 修飾,表示它是一個私用的方法,只能在物件內部使用,私用方法通常是演算法中的一小段可重用程式碼,在類別內部定義私用方法以管理這小段可重用程式碼,對於操作公開方法的外部物件來說,不必知道這些私用方法的存在。

圖11-4 透過公開方法完成操作

透過公開方法完成操作,可實現對演算法的封裝性,即使您改變了某個操作內部的演算法,對操作該方法的物件並不會影響,因為呼叫的公開介面並沒有改變。

方法前的權限修飾可以省略,如果省略的話,預設就是同一個套件管理下的類別才可以呼叫該方法。

• 使用所定義的類別

實際撰寫個程式來使用程式碼 11-1 所定義的類別,首先您必須編譯完程式碼 11-1,並注意產生的*.class 檔案必須是在 Classpath 設定的路徑之中,接下來您可以撰寫程式碼 11-2 來測試您所撰寫的 Hello 類別:

public class HelloDemo{
    public static void main(String[] args){
        // 指定引數建立 hello 實例
        Hello hello = new Hello("Java");
        // 呼叫 hello()方法
        hello.hello();

        // 呼叫 setName()改變 name 的參考對象
        hello.setName("caterpillar");
        hello.hello();
    }
}
                            
程式碼 11-2 HelloDemo.java

假設您並不是 Hello 類別的設計人員,今天您只要知道 Hello 類別上有什麼公開方法,就可以直接實例化它並加以操作,您在前幾個單元也就是這麼操作 Java SE 提供的一些物件的,所以設計良好的類別可以提高程式碼的重用性,減輕重複開發相同功能元件的負擔。

圖11-5 程式碼 11-2 的執行結果