11-6 多型操作、抽象方法與抽象類別

如果類別與類別之間有繼承關係,則您可以用父類別宣告一個參考名稱,並讓其參考至子類別的實例,並以父類別上的公開介面來操作子類別上對應的公開方法,這是多型( Polymorphism )操作的基本方式。

更進一步的,您可以在父類別中事先規範子類別必須實作的方法,父類別中暫時無需實作,這樣的方法稱之為抽象方法( Abstract method ),它的目的是先規範操作介面,並在執行時期可以操作各種子類別的實例,包括有抽象方法的類別則稱之為抽象類別( Abstract class )

• 多型導論

在前一個小節您知道在 Java 中,Object 類別是所有類別最頂層的父類別,您可以使用 Object 類別來宣告一個參考名稱,並讓它參考至所有的物件,例如以下的程式碼是可行的:

Object obj = "Java is funny!";
                            

"Java is funny!"在執行時期會產生一個 String 實例作為代表,您可以讓它被 Object 所宣告的名稱 obj 參考而不會有任何的錯誤,在 Java 中您可以讓父類別所宣告的參考名稱,參考至子類別的實例。

Object 上有 toString()、hashCode()等方法,而 String 也繼承了這些方法,由於擁有同樣的操作方法,因而您可以透過obj 名稱來操作 toString()、hashCode()等方法,例如程式碼11-10 所示範的:

public class PolymorphismDemo {
    public static void main(String[] args) {
        Object obj1 =
                new String("Java Everywhere");
        Object obj2 =
                new String("Java Everywhere");

        System.out.println(obj1.toString());
        System.out.println(obj1.hashCode());
        System.out.println(obj1.equals(obj2));
    }
}
                            
程式碼 11-18 PolymorphismDemo.java

bj1 名稱是 Object 型態,即使如此您仍可以正確的操作String 實例上擁有的共同方法,即 Object 上已規範的方法,所以程式碼第 8 行會顯示"Java Everywhere" ,第 9 行會顯示雜湊碼,第 10 行會進行字串內容值的比較,結果如下所示:

圖11-21 程式碼 11-18 的執行結果

像這樣透過 Object 型態的參考名稱來操作 String 子類的實例,就是多型(Polymorphism)操作的一個實際例子,由於您所使用的是 Object 型態的介面,所以無法操作 String 子類別上自己定義的 toUpperCase()、toLowerCase()等方法,Object 型態的名稱並不認識那些方法。

圖11-22 子類別上特定的方法無法透過父類介面來操作

如果您要操作子類別上的特定方法時,您要先轉換操作的介面,例如:

Object obj = "Java is funny!";
String str = (String) obj;
System.out.println(str.toUpperCase());
                            

這一次使用的是 String 態的參考名稱,因而可以正確操作toUpperCase() 方法了,結果會在螢幕上顯示 "JAVA IS FUNNY!"的文字。

圖11-23 使用 String 參考名稱來操作 String 物件

• 抽象方法、抽象類別

在定義類別的時候,您也許會先定義出一些子類別必須共同遵守的行為,但父類別的目的只是先定義,在父類別中並不打算實作這些行為,此時您可以將這些行為定義為抽象方法( Abstract method )

在定義方法時,您可以使用關鍵字 abstract 來修飾它成為抽象方法,而一個含有抽象方法的類別則稱之為抽象類別( Abstract class ),抽象類別不能被實例化,它只能被繼承,繼承抽象類別的子類別必須實作抽象類別中所有的抽象方法。

程式碼 11-19 示範了抽象方法與抽象類別的一個實際例子,您設計了一個 GuessGame 類別,當中在 start()方法中先定義了猜數字的遊戲流程,但對於如何取得使用者輸入,以及如何顯示猜錯或猜對的訊息等方法,您希望子類別中自己定義。

public abstract class GuessGame {
    private int number;

    public void setNumber(int number) {
        this.number = number;
    }

    public void start() {
        int guess = 0;
        do {
            guess = userInput();
            if (guess > number) {
                bigger();
            }
            else if (guess < number) {
                smaller();
            }
            else {
                right();
            }
        } while (guess != number);
    }
    // 抽象方法
    protected abstract void bigger();
    protected abstract void smaller();
    protected abstract void right();
    protected abstract int userInput();
}
                            
程式碼 11-19 GuessGame.java

程式碼第 1 行使用 abstract 將類別修飾為抽象類別,而程式碼第 24 行到第 27 行則使用 abstract 定義了抽象方法,可以看到抽象方法並不用實作,直接以分號作結即可。

抽象類別無法被實例化,使用它的方法是繼承它,例如程式碼 11-20 繼承 GuessGame 實作一個文字模式下的猜數字遊戲。

public class TextGuessGame extends GuessGame {
    private java.util.Scanner scanner;

    public TextGuessGame() {
        scanner =
                new java.util.Scanner(System.in);
    }
    // 實作抽象方法
    public void bigger() {
        System.out.println(
                "輸入數字比較目標數字大");
    }
    public void smaller() {
        System.out.println(
                "輸入數字比較目標數字小");
    }
    public void right() {
        System.out.println("恭喜!猜中了!");
    }
    public int userInput() {
        System.out.print("輸入數字: ");
        return scanner.nextInt();
    }
}
                            
程式碼 11-20 TextGuessGame.java

繼承了 GuessGame 的 TextGuessGame 類別必須實作bigger()、smaller()、right()與 userInput()方法,您可以在程式碼 11-21 中看到,子類別如何實作,對於父類別中定義的流程並不影響。

public class GuessGameDemo {
    public static void main(String[] args) {
        GuessGame game = new TextGuessGame();

        game.setNumber(456);
        System.out.println("猜數字 0.1 版...");

        game.start();
    }
}
                            
程式碼 11-21 GuessGameDemo.java

注意到名稱 game 的型態是 GuessGame , 由於TextGuessGame 物件的所有方法在父類別中都有規範,因而 game 可以操作 TextGuessGame 上的所有方法,這是多型操作的例子,一個執行的範例如下:

圖11-24 程式碼 11-21 的執行結果

如果今天您打算製作一個視窗模式的猜數字遊戲,您可以繼承 GuessGame 類別實作一個 WindowGuessGame,並在抽象方法中實作視窗模式的訊息顯示,最後將程式碼 11-21 的第一行改為以下:

GuessGame game = new WindowGuessGame();
                            

其餘的程式無須修改,就可以直接操作 WindowGuessGame 物件來完成視窗模式的猜數字遊戲。

相關資料

下列參考資料能夠對本章節所討論的話題提供 更詳細的說明: