重新定義實作


現在有個需求,請設計static方法,可以播放角色攻擊動畫,你也許會這麼想,學剛剛多型的寫法,設計個drawFight()方法如何?

Role沒有定義fight()方法


drawFight()方法而言,只知道傳進來的會是一種Role物件,所以編譯器也只能檢查你呼叫的方法,Role是不是有定義,顯然地,Role目前並沒有定義fight()方法,因此編譯錯誤了。

然而你仔細觀察一下SwordsManMagicianfight()方法,他們的方法簽署(method signature)都是:

public void fight()

也就是說,操作介面是相同的,只是方法實作內容不同,你可以將fight()方法提昇至Role類別中定義:

package cc.openhome;

public class Role {
    ...略
    public void fight() {
        // 子類別要重新定義fight()的實際行為
    }
}

Role類別中定義了fight()方法,由於實際上角色如何攻擊,只有子類別才知道,所以這邊的fight()方法內容是空的,沒有任何程式碼實作。SwordsMan繼承Role之後,再對fight()的實作進行定義:

package cc.openhome;

public class SwordsMan extends Role {
    public void fight() {
        System.out.println("揮劍攻擊");
    }
}

在繼承父類別之後,定義與父類別中相同的方法簽署,但實作內容不同,這稱為重新定義(Override),因為對父類別中已定義的方法實作不滿意,所以你在子類別中重新定義實作。Magician繼承Role之後,也重新定義了fight()的行為:

package cc.openhome;

public class Magician extends Role {
    public void fight() {
        System.out.println("魔法攻擊");
    }
    ...略 
}

由於Role現在定義了fight()方法(雖然方法區塊中沒有程式碼實作),所以編譯器不會找不到Rolefight()了,因此你可以如下撰寫:

package cc.openhome;

public class RPG {
    public static void main(String[] args) {
        SwordsMan swordsMan = new SwordsMan();
        swordsMan.setName("Justin");
        swordsMan.setLevel(1);
        swordsMan.setBlood(200);

        Magician magician = new Magician();
        magician.setName("Monica");
        magician.setLevel(1);
        magician.setBlood(100);
        
        drawFight(swordsMan);
        drawFight(magician);
    }

    static void drawFight(Role role) {
        System.out.print(role.getName());
        role.fight();
    }
}

fight()方法宣告了Role型態的參數,那方法中呼叫的,到底是Role中定義的fight(),還是個別子類別中定義的fight()呢?如果傳入fight()的是SwordsManrole參數參考的就是SwordsMan實例,操作的就是SwordsMan上的方法定義。這就好比role牌子掛在SwordsMan實例身上,你要求有role牌子的物件攻擊,發動攻擊的物件就是SwordsMan實例。同樣地,如果傳入fight()的是Magicianrole參數參考的就是Magician實例,操作的就是Magician上的方法定義。

所以範例最後的執行結果是:

Justin揮劍攻擊
Monica魔法攻擊

在重新定義父類別中某個方法時,子類別必須撰寫與父類別方法中相同的簽署,然而如果疏忽打錯字了:

public class SwordsMan extends Role {
    public void Fight() {
        System.out.println("揮劍攻擊");
    }
}

以這邊的例子來說,父類別中定義的是fight(),但子類別中定義了Fight(),這就不是重新定義fight()了,而是子類別新定義了一個Fight()方法,這是合法的方法定義,編譯器並不會發出任何錯誤訊息,你只會在運行範例時,發現為什麼SwordsMan完全沒有攻擊。

在JDK5之後支援標註(Annotation),其中一個內建的標準標註就是@Override,如果你在子類別中某個方法前標註@Override,表示要求編譯器檢查,該方法是不是真的重新定義了父類別中某個方法,如果不是的話,就會引發編譯錯誤。例如:

編譯器檢查是否真的重新定義父類別某方法