如果類別與類別之間有繼承關係,則您可以用父類別宣告一個參考名稱,並讓其參考至子類別的實例,並以父類別上的公開介面來操作子類別上對應的公開方法,這是多型( 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 行會進行字串內容值的比較,結果如下所示:
像這樣透過 Object 型態的參考名稱來操作 String 子類的實例,就是多型(Polymorphism)操作的一個實際例子,由於您所使用的是 Object 型態的介面,所以無法操作 String 子類別上自己定義的 toUpperCase()、toLowerCase()等方法,Object 型態的名稱並不認識那些方法。
如果您要操作子類別上的特定方法時,您要先轉換操作的介面,例如:
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 上的所有方法,這是多型操作的例子,一個執行的範例如下:
如果今天您打算製作一個視窗模式的猜數字遊戲,您可以繼承 GuessGame 類別實作一個 WindowGuessGame,並在抽象方法中實作視窗模式的訊息顯示,最後將程式碼 11-21 的第一行改為以下:
GuessGame game = new WindowGuessGame();
其餘的程式無須修改,就可以直接操作 WindowGuessGame 物件來完成視窗模式的猜數字遊戲。
下列參考資料能夠對本章節所討論的話題提供 更詳細的說明: