11-4 父類別、子類別

服飾公司的老闆決定要擴大公司的產品線,除了衣服之外,還要販賣帽子、短襪以及褲子等。他意識到這樣可能要改變您先前所發展的銷售系統。

您先前所開發的系統都是銷售衣服,您也許已經定義了Clothes 類別,當中包括了尺寸、價格等資訊,然而即將販賣的帽子、短襪以及褲子等都是「衣服類」,它們同樣擁有尺寸、價格等資訊。

如果您的程式是使用 Java 物件導向程式語言來開發,很幸運的,您可以使用繼承的機制輕易的擴充您的系統,您只要為帽子、短襪以及褲子等定義類別,並讓它們直接繼承Clothes 類別,就可以擁有原來 Clothers 類別中所定義的特性。

• 擴充父類別

在 Java 語言中,使用 extends 關鍵字來表示此類別繼承自其它類別。為了宣告類別為某個類別的子類別,在您的類別宣告就必須使用底下的語法:

[class_modifier] class class_identifier extends superclass_identifier
                            

其中:

  • class_modifier 是可有可無的,也許是 public、abstract 或 final。
  • class 關鍵字告訴編譯器此程式區塊是類別宣告。
  • class_identifier 是您為此子類別命名的名稱。
  • extends 關鍵字告訴編譯器此類別為其它類別的子類別。
  • superclass_identifier 是此子類別所繼承之父類別的名稱。

以實際的例子來進行說明,假設您的系統中原先已有定義 Clothes 類別,如程式碼 11-9 所示:

public class Clothes {
    private char size;
    private double price;

    public void setSize(char size) {
        this.size = size;
    }

    public char getSize() {
        return size;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }
}
                            
程式碼 11-9 Clothes.java

現在您打算增加一個褲子產品,褲子也算是衣服的一類,也同樣具有尺寸與價格的屬性,但要多一個腰圍大小,您可以直接繼承 Clothes 類別來定義 Pants,並增加一個 waistline 屬性,如程式碼 11-10 所示:

public class Pants extends Clothes {
    private int waistline;

    public void setWaistline(int waistline) {
        this.waistline = waistline;
    }
    public int getWaistline() {
        return waistline;
    }
}
                            
程式碼 11-10 Pants.java

Pants 類別繼承了 Clothes 類別的 size、price 資料成員,並增加了自己的 waistline 成員,而 Clothes 類別中的公開方法也被繼承了下來,並增加了 setWaistline()、getWaistline()兩個公開方法,您可以依同樣的方式,為您的系統增加 Hat、Skirt 等類別。

在名稱上,Clothes 稱之為父類別( Parent class )基礎類別( Base class ),而 Pants 則稱之為子類別( Child class )衍生類別( Derived class )

圖11-12 父類別、子類別關係

在繼承了 Clothes 類別之後,對於 Clothes 中原先公開的( public )成員您可以直接呼叫使用,而對於 Clothes 中原先設定為私用的( private )成員,您無法直接呼叫使用,而必須靠原先 Clothes 中提供的公開操作方法來進行存取,一個例子如程式碼 11-11 所示:

public class PantsDemo {
    public static void main(String[] args) {
        Pants pants = new Pants();
        // 從父類別繼承下來的方法
        pants.setSize('L');
        pants.setPrice(599.0);
        // 新增的方法
        pants.setWaistline(28);
        System.out.println("尺寸:"
                + pants.getSize());
        System.out.println("價格:"
                + pants.getPrice());
        System.out.println("腰圍:"
                + pants.getWaistline());
    }
}
                            
程式碼 11-11 PantsDemo.java

可以看到繼承的好處,在 Pants 中並沒有自行定義setSize()、setPrice()等方法,它仍可以直接使用從 Clothes 繼承下來的這些方法,程式碼 11-11 的執行結果如下所示:

圖11-13 程式碼 11-11 的執行結果

• protected 成員

在父類別中的私用(private)成員在繼承後,無法於子類別中直接呼叫使用,如果您希望某成員在被繼承之後,子類別可以直接呼叫使用,則您可以將該成員設定為 protected,一個 protected 成員在繼承後,可以於子類別中直接呼叫使用,但對外部物件仍是保護狀態,不讓外部物件直接呼叫。

例如可以將程式碼 11-9 修改為程式碼 11-12 的內容,將兩個資料成員修改為 protected 權限:

public class Clothes1 {
    protected char size;
    protected double price;

    public void setSize(char size) {
        this.size = size;
    }
    public char getSize() {
        return size;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public double getPrice() {
        return price;
    }
}
                            
程式碼 11-12 Clothes1.java

如果有個 Skirt 類別繼承了 Clothes1 類別,則它可以在類別中直接存取 size 與 price 成員,而不必再透過公開的方法來存取。

• 重新定義方法

在繼承了父類別之後,如果您對於父類別中的某些方法並不滿意,則您可以在繼承之後重新定義( Override )該方法,或稱之為改寫。舉個例子來說,對於程式碼 11-12 的 setSize() 與 setPrice()方法,為了避免銷售人員輸入錯誤,您也許會希望在繼承它之後重新定義,加入一些簡單的判斷,如程式碼 11-13 所示:

public class Pants1 extends Clothes1 {
    protected int waistline;

    // 重新定義 setSize()
    public void setSize(char size) {
        if (size != 'L' && size != 'M' && size != 'S') {
            this.size = 'N';
        }
        else {
            this.size = size;
        }
    }
    // 重新定義 setPrice()
    public void setPrice(double price) {
        if (price < 0.0) {
            price = 0.0;
        }
        else {
            this.price = price;
        }
    }
    public void setWaistline(int waistline) {
        this.waistline = waistline;
    }
    public int getWaistline() {
        return waistline;
    }
}
                            
程式碼 11-13 Pants1.java

這麼一來,當您在操作 Pants1 型態的物件時,所執行的就是您在 Pants1 中所重新定義的 setSize()與 setPrice()方法,例如:

public class Pants1Demo {
    public static void main(String[] args) {
        Pants1 pants1 = new Pants1();
        // 呼叫的是重新定義的方法
        pants1.setSize('X');
        pants1.setPrice(-100.0);
        // 新增的方法
        pants1.setWaistline(28);

        System.out.println("尺寸:"
                + pants1.getSize());
        System.out.println("價格:"
                + pants1.getPrice());
        System.out.println("腰圍:"
                + pants1.getWaistline());
    }
}

                            
程式碼 11-14 Pants1Demo.java

在程式碼中的第 5 行、第 6 行故意輸入了錯誤的資料,依Pants1 類別中重新定義的 setSize()與 setPrice()之定義,會將 size 與 price 設定為'N'與 0.0,執行結果如下所示:

圖11-14 程式碼 11-14 的執行結果

重新定義方法時要注意的是,在繼承之後您可以於子類別中增大原先父類別已有的方法權限,但不可以縮小權限,也就是說原來父類別中如果是 public 的方法,則您不可以於重新定義時使用 protected 或 private 修飾,否則會出現以下的錯誤訊息:

...attempting to assign weaker accessprivileges;...

• super 關鍵字

在繼承某類別之後,如果您打算呼叫父類別中原先已定義的建構式或方法,則您可以使用 super 關鍵字,例如,如果父類別是這麼定義:

public class Parent {
    public Parent(String name) {
        // ....
    }
}
                            

則繼承了 Parent 類別之後,您想要在建構子類別實例時,先執行 Parent 類別中的建構式,則您可以這麼撰寫:

public class Child extends Parent {
    public Child(String parentName, String givenName) {
        super(parentName);
        // ....
    }
}
                            

如果您在繼承了父類別之後,重新定義父類別中繼承下來的某個方法,而您也許又想執行原來父類別中定義的方法,則您也可以使用 super 來進行呼叫,例如若原先父類別中定義了 someMethod()方法:

public class Parent {
    public void someMethod() {
        System.out.println("someMethod");
    }
}
                            

您繼承了 Parnet 類別後,可以使用 super 關鍵字加上 .運算子,之後接上方法名稱來呼叫原先父類別中的方法,例如:

public class Child extends Parent
    public void someMethod() {
        super.someMethod();
        System.out.println("someMethod again");
    }
}
                            

如果您實例化 Child 類別,並呼叫 someMethod()方法的話,則結果會於螢幕上顯示一行 "someMethod" 與一行"someMethod again"的文字。

圖11-15 使用 super 關鍵字

• 終止重新定義、終止繼承

如果您在設計類別的時候,有個方法並不想讓子類別繼承後重新定義,則您可以在方法上加上"final",例如:

public class Parent {
    public final void someMethod() {
        // ...
    }
}
                            

繼承 Parent 類別的子類別可以使用 someMethod(),但無法重新定義 someMethod()。

如果您希望某個類別完全不能被繼承,則可以在定義類別時加上"final"關鍵字,例如:

public final class SomeClass {
    //
}
                            

被 final 修飾的類別無法被繼承,在 Java SE 中的一個實際例子就是 java.lang.String 類別,您無法繼承 String 類別來定義您自己的子類別。